@web-auto/webauto 0.1.8 → 0.1.11
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/apps/desktop-console/dist/main/index.mjs +909 -105
- package/apps/desktop-console/dist/main/preload.mjs +3 -0
- package/apps/desktop-console/dist/renderer/index.html +9 -1
- package/apps/desktop-console/dist/renderer/index.js +796 -331
- package/apps/desktop-console/entry/ui-cli.mjs +59 -9
- package/apps/desktop-console/entry/ui-console.mjs +8 -3
- package/apps/webauto/entry/account.mjs +70 -9
- package/apps/webauto/entry/lib/account-detect.mjs +106 -25
- package/apps/webauto/entry/lib/account-store.mjs +122 -35
- package/apps/webauto/entry/lib/profilepool.mjs +45 -13
- package/apps/webauto/entry/lib/schedule-store.mjs +1 -25
- package/apps/webauto/entry/profilepool.mjs +45 -3
- package/apps/webauto/entry/schedule.mjs +44 -2
- package/apps/webauto/entry/weibo-unified.mjs +2 -2
- package/apps/webauto/entry/xhs-install.mjs +248 -52
- package/apps/webauto/entry/xhs-unified.mjs +33 -6
- package/bin/webauto.mjs +137 -5
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/dist/services/unified-api/server.js +5 -0
- package/dist/services/unified-api/task-state.js +2 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +142 -14
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +16 -1
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +104 -0
- package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -4
- package/modules/camo-runtime/src/autoscript/schema.mjs +9 -0
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +9 -2
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +107 -1
- package/modules/camo-runtime/src/container/runtime-core/subscription.mjs +24 -2
- package/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
- package/package.json +7 -3
- package/runtime/infra/utils/README.md +13 -0
- package/runtime/infra/utils/scripts/README.md +0 -0
- package/runtime/infra/utils/scripts/development/eval-in-session.mjs +40 -0
- package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +35 -0
- package/runtime/infra/utils/scripts/service/kill-port.mjs +24 -0
- package/runtime/infra/utils/scripts/service/start-api.mjs +103 -0
- package/runtime/infra/utils/scripts/service/start-browser-service.mjs +173 -0
- package/runtime/infra/utils/scripts/service/stop-api.mjs +30 -0
- package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +104 -0
- package/runtime/infra/utils/scripts/test-services.mjs +94 -0
- package/scripts/bump-version.mjs +120 -0
- package/services/unified-api/server.ts +4 -0
- package/services/unified-api/task-state.ts +5 -0
|
@@ -75,7 +75,7 @@ function renderPreflight(root, ctx2) {
|
|
|
75
75
|
const batchDeleteFp = createEl("input", { type: "checkbox" });
|
|
76
76
|
const onboardingSummary = createEl("div", { className: "muted" }, ["\u6B63\u5728\u52A0\u8F7D profile \u4FE1\u606F..."]);
|
|
77
77
|
const onboardingTips = createEl("div", { className: "muted", style: "font-size:12px; margin-top:6px;" }, [
|
|
78
|
-
"\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\
|
|
78
|
+
"\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\uFF1AProfile \u4F7F\u7528\u4E2D\u6027\u547D\u540D profile-0/1/2\uFF1B\u767B\u5F55\u540E\u53EF\u8BBE\u7F6E alias\uFF08\u8D26\u53F7\u540D\uFF09\u7528\u4E8E\u533A\u5206\uFF0C\u9ED8\u8BA4\u4F1A\u81EA\u52A8\u83B7\u53D6\u7528\u6237\u540D\u3002"
|
|
79
79
|
]);
|
|
80
80
|
const gotoXhsBtn = createEl("button", { className: "secondary", type: "button" }, ["\u53BB\u5C0F\u7EA2\u4E66\u9996\u9875"]);
|
|
81
81
|
const browserStatus = createEl("div", { className: "muted" }, ["\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u672A\u68C0\u67E5"]);
|
|
@@ -372,7 +372,7 @@ ${mergedOutput}`);
|
|
|
372
372
|
createEl("div", { className: "muted" }, [`\u63D0\u793A\uFF1Aprofile \u4E0E fingerprint \u7684\u771F\u5B9E\u8DEF\u5F84\u5747\u5728 ${webautoRoot} \u4E0B\uFF1Balias \u53EA\u5F71\u54CD UI \u663E\u793A\uFF0C\u4E0D\u5F71\u54CD profileId\u3002`])
|
|
373
373
|
])
|
|
374
374
|
);
|
|
375
|
-
const keywordInput = createEl("input", { value: "
|
|
375
|
+
const keywordInput = createEl("input", { value: "profile", placeholder: "Profile \u524D\u7F00\uFF08\u53EF\u9009\uFF09\uFF0C\u9ED8\u8BA4 profile\uFF0C\u751F\u6210\u5982 profile-0/profile-1" });
|
|
376
376
|
const ensureCountInput = createEl("input", { value: "0", type: "number", min: "0" });
|
|
377
377
|
const timeoutInput = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
378
378
|
const keepSession = createEl("input", { type: "checkbox" });
|
|
@@ -414,10 +414,13 @@ ${mergedOutput}`);
|
|
|
414
414
|
if (!createdProfileId) return;
|
|
415
415
|
if (typeof window.api?.cmdSpawn !== "function") return;
|
|
416
416
|
const timeoutSec = Math.max(30, Math.floor(Number(timeoutInput.value || "900")));
|
|
417
|
+
const idleTimeout = String(window.api?.settings?.idleTimeout || "30m").trim() || "30m";
|
|
417
418
|
const loginArgs = buildArgs([
|
|
418
419
|
window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
419
420
|
"login-profile",
|
|
420
421
|
createdProfileId,
|
|
422
|
+
"--idle-timeout",
|
|
423
|
+
idleTimeout,
|
|
421
424
|
"--timeout-sec",
|
|
422
425
|
String(timeoutSec),
|
|
423
426
|
"--check-interval-sec",
|
|
@@ -1019,6 +1022,7 @@ function renderSettings(root, ctx2) {
|
|
|
1019
1022
|
const keyword = createEl("input", { value: ctx2.settings?.defaultKeyword || "" });
|
|
1020
1023
|
const loginTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
1021
1024
|
const cmdTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.cmdTimeoutSec || 0), type: "number", min: "0" });
|
|
1025
|
+
const idleTimeout = createEl("input", { value: ctx2.settings?.idleTimeout || "30m", placeholder: "30m" });
|
|
1022
1026
|
const aiEnabled = createEl("input", { type: "checkbox", checked: ctx2.settings?.aiReply?.enabled ?? false });
|
|
1023
1027
|
const aiBaseUrl = createEl("input", { value: ctx2.settings?.aiReply?.baseUrl || "http://127.0.0.1:5520", placeholder: "http://127.0.0.1:5520" });
|
|
1024
1028
|
const aiApiKey = createEl("input", { value: ctx2.settings?.aiReply?.apiKey || "", type: "password", placeholder: "sk-..." });
|
|
@@ -1080,6 +1084,7 @@ function renderSettings(root, ctx2) {
|
|
|
1080
1084
|
loginTimeoutSec: Number(loginTimeout.value || "900"),
|
|
1081
1085
|
cmdTimeoutSec: Number(cmdTimeout.value || "0")
|
|
1082
1086
|
},
|
|
1087
|
+
idleTimeout: idleTimeout.value.trim() || "30m",
|
|
1083
1088
|
aiReply: {
|
|
1084
1089
|
enabled: aiEnabled.checked,
|
|
1085
1090
|
baseUrl: aiBaseUrl.value.trim(),
|
|
@@ -1107,7 +1112,8 @@ function renderSettings(root, ctx2) {
|
|
|
1107
1112
|
]),
|
|
1108
1113
|
createEl("div", { className: "row" }, [
|
|
1109
1114
|
labeledInput("loginTimeoutSec", loginTimeout),
|
|
1110
|
-
labeledInput("cmdTimeoutSec", cmdTimeout)
|
|
1115
|
+
labeledInput("cmdTimeoutSec", cmdTimeout),
|
|
1116
|
+
labeledInput("idleTimeout (e.g., 30m, 1h)", idleTimeout)
|
|
1111
1117
|
]),
|
|
1112
1118
|
createEl("div", { className: "row" }, [
|
|
1113
1119
|
createEl("button", {}, ["\u4FDD\u5B58"])
|
|
@@ -1524,12 +1530,13 @@ function normalizeRow(row) {
|
|
|
1524
1530
|
updatedAt: asText(row?.updatedAt)
|
|
1525
1531
|
};
|
|
1526
1532
|
}
|
|
1527
|
-
async function listAccountProfiles(api) {
|
|
1533
|
+
async function listAccountProfiles(api, options = {}) {
|
|
1528
1534
|
const script = api.pathJoin("apps", "webauto", "entry", "account.mjs");
|
|
1535
|
+
const platform = asText(options?.platform);
|
|
1529
1536
|
const out = await api.cmdRunJson({
|
|
1530
1537
|
title: "account list",
|
|
1531
1538
|
cwd: "",
|
|
1532
|
-
args: [script, "list", "--json"],
|
|
1539
|
+
args: [script, "list", ...platform ? ["--platform", platform] : [], "--json"],
|
|
1533
1540
|
timeoutMs: 2e4
|
|
1534
1541
|
});
|
|
1535
1542
|
const rows = Array.isArray(out?.json?.profiles) ? out.json.profiles : [];
|
|
@@ -1537,6 +1544,14 @@ async function listAccountProfiles(api) {
|
|
|
1537
1544
|
}
|
|
1538
1545
|
|
|
1539
1546
|
// src/renderer/tabs-new/setup-wizard.mts
|
|
1547
|
+
function formatProfileTag(profileId) {
|
|
1548
|
+
const id = String(profileId || "").trim();
|
|
1549
|
+
const m = id.match(/^profile-(\d+)$/i);
|
|
1550
|
+
if (!m) return id;
|
|
1551
|
+
const seq = Number(m[1]);
|
|
1552
|
+
if (!Number.isFinite(seq)) return id;
|
|
1553
|
+
return `P${String(seq).padStart(3, "0")}`;
|
|
1554
|
+
}
|
|
1540
1555
|
function renderSetupWizard(root, ctx2) {
|
|
1541
1556
|
root.innerHTML = "";
|
|
1542
1557
|
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
@@ -1574,7 +1589,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1574
1589
|
<div class="env-item" id="env-firefox" style="display:flex; align-items:center; justify-content:space-between; gap:8px;">
|
|
1575
1590
|
<span style="display:flex; align-items:center; gap:8px; min-width:0;">
|
|
1576
1591
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1577
|
-
<span class="env-label"
|
|
1592
|
+
<span class="env-label">\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox Firefox\uFF09</span>
|
|
1578
1593
|
</span>
|
|
1579
1594
|
<button id="repair-runtime-btn" class="secondary" style="display:none; flex:0 0 auto;">\u4E00\u952E\u4FEE\u590D</button>
|
|
1580
1595
|
</div>
|
|
@@ -1648,30 +1663,31 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1648
1663
|
let envCheckInFlight = false;
|
|
1649
1664
|
let accountCheckInFlight = false;
|
|
1650
1665
|
let busUnsubscribe = null;
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
geoip: !snapshot?.geoip?.installed
|
|
1660
|
-
});
|
|
1666
|
+
const getMissing = (snapshot) => snapshot?.missing || {
|
|
1667
|
+
core: true,
|
|
1668
|
+
runtimeService: true,
|
|
1669
|
+
camo: true,
|
|
1670
|
+
runtime: true,
|
|
1671
|
+
geoip: true
|
|
1672
|
+
};
|
|
1673
|
+
const isEnvReady = (snapshot) => Boolean(snapshot?.allReady);
|
|
1661
1674
|
async function collectEnvironment() {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1675
|
+
if (typeof ctx2.api?.envCheckAll !== "function") {
|
|
1676
|
+
throw new Error("envCheckAll unavailable");
|
|
1677
|
+
}
|
|
1678
|
+
const snapshot = await ctx2.api.envCheckAll();
|
|
1679
|
+
if (snapshot && typeof snapshot === "object" && snapshot.camo && snapshot.services) {
|
|
1680
|
+
return snapshot;
|
|
1681
|
+
}
|
|
1682
|
+
throw new Error("invalid envCheckAll response");
|
|
1669
1683
|
}
|
|
1670
1684
|
function applyEnvironment(snapshot) {
|
|
1685
|
+
const browserReady = Boolean(snapshot.browserReady);
|
|
1686
|
+
const browserDetail = snapshot.firefox?.installed ? "\u5DF2\u5B89\u88C5" : snapshot.services?.camoRuntime ? "\u7531 Runtime \u670D\u52A1\u63D0\u4F9B" : "\u672A\u5B89\u88C5";
|
|
1671
1687
|
updateEnvItem("env-camo", snapshot.camo?.installed, snapshot.camo?.version || (snapshot.camo?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5"));
|
|
1672
1688
|
updateEnvItem("env-unified", snapshot.services?.unifiedApi, "7701");
|
|
1673
1689
|
updateEnvItem("env-browser", snapshot.services?.camoRuntime, "7704");
|
|
1674
|
-
updateEnvItem("env-firefox",
|
|
1690
|
+
updateEnvItem("env-firefox", browserReady, snapshot.firefox?.path || browserDetail);
|
|
1675
1691
|
updateEnvItem("env-geoip", snapshot.geoip?.installed, snapshot.geoip?.installed ? "\u5DF2\u5B89\u88C5\uFF08\u53EF\u9009\uFF09" : "\u672A\u5B89\u88C5\uFF08\u53EF\u9009\uFF09");
|
|
1676
1692
|
envReady = isEnvReady(snapshot);
|
|
1677
1693
|
syncRepairButtons(snapshot);
|
|
@@ -1720,7 +1736,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1720
1736
|
}
|
|
1721
1737
|
async function repairInstall({ browser, geoip, reinstall, uninstall }) {
|
|
1722
1738
|
if (typeof ctx2.api?.envRepairDeps === "function") {
|
|
1723
|
-
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\
|
|
1739
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox\uFF09...";
|
|
1724
1740
|
const res = await ctx2.api.envRepairDeps({
|
|
1725
1741
|
browser: Boolean(browser),
|
|
1726
1742
|
geoip: Boolean(geoip),
|
|
@@ -1732,7 +1748,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1732
1748
|
return { ok, detail };
|
|
1733
1749
|
}
|
|
1734
1750
|
if (typeof ctx2.api?.cmdRunJson === "function") {
|
|
1735
|
-
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\
|
|
1751
|
+
setupStatusText.textContent = reinstall ? "\u6B63\u5728\u5378\u8F7D\u5E76\u91CD\u88C5\u8D44\u6E90\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip && browser ? "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08\u6D4F\u89C8\u5668\u5185\u6838/GeoIP\uFF09..." : geoip ? "\u6B63\u5728\u5B89\u88C5 GeoIP\uFF08\u53EF\u9009\uFF09..." : "\u6B63\u5728\u5B89\u88C5\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox\uFF09...";
|
|
1736
1752
|
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "xhs-install.mjs");
|
|
1737
1753
|
const args = [script];
|
|
1738
1754
|
if (reinstall) args.push("--reinstall");
|
|
@@ -1810,14 +1826,14 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1810
1826
|
applyEnvironment(latest);
|
|
1811
1827
|
updateCompleteStatus();
|
|
1812
1828
|
if (!detail) {
|
|
1813
|
-
if (label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1814
|
-
ok = Boolean(latest.
|
|
1815
|
-
} else if (label.includes("
|
|
1829
|
+
if (label.includes("\u6D4F\u89C8\u5668\u5185\u6838") || label.includes("Camoufox") || label.includes("Runtime")) {
|
|
1830
|
+
ok = Boolean(latest.browserReady);
|
|
1831
|
+
} else if (label.includes("CLI") || label.includes("camo")) {
|
|
1816
1832
|
ok = Boolean(latest.camo?.installed);
|
|
1817
1833
|
} else if (label.includes("\u6838\u5FC3")) {
|
|
1818
1834
|
ok = Boolean(latest.services?.unifiedApi && latest.services?.camoRuntime);
|
|
1819
1835
|
} else {
|
|
1820
|
-
ok =
|
|
1836
|
+
ok = Boolean(latest.allReady);
|
|
1821
1837
|
}
|
|
1822
1838
|
}
|
|
1823
1839
|
}
|
|
@@ -1840,12 +1856,13 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1840
1856
|
applyEnvironment(snapshot);
|
|
1841
1857
|
updateCompleteStatus();
|
|
1842
1858
|
if (!envReady) {
|
|
1859
|
+
const missingFlags = getMissing(snapshot);
|
|
1843
1860
|
const missing = [];
|
|
1844
|
-
if (
|
|
1845
|
-
if (
|
|
1846
|
-
if (
|
|
1861
|
+
if (missingFlags.camo) missing.push("camo-cli");
|
|
1862
|
+
if (missingFlags.core) missing.push("unified-api");
|
|
1863
|
+
if (missingFlags.runtime) missing.push("browser-kernel");
|
|
1847
1864
|
setupStatusText.textContent = `\u5B58\u5728\u5F85\u4FEE\u590D\u9879: ${missing.join(", ")}`;
|
|
1848
|
-
if (
|
|
1865
|
+
if (missingFlags.runtimeService) {
|
|
1849
1866
|
setupStatusText.textContent += "\uFF08camo-runtime \u672A\u5C31\u7EEA\uFF0C\u5F53\u524D\u4E3A\u53EF\u9009\uFF09";
|
|
1850
1867
|
}
|
|
1851
1868
|
} else if (!snapshot?.geoip?.installed) {
|
|
@@ -1917,8 +1934,8 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1917
1934
|
style: "display:flex; justify-content:space-between; align-items:center; padding:8px 12px; border-bottom:1px solid var(--border);"
|
|
1918
1935
|
}, [
|
|
1919
1936
|
createEl("div", {}, [
|
|
1920
|
-
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || acc.profileId]),
|
|
1921
|
-
createEl("div", { className: "muted", style: "font-size:11px;" }, [acc.profileId])
|
|
1937
|
+
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || formatProfileTag(acc.profileId)]),
|
|
1938
|
+
createEl("div", { className: "muted", style: "font-size:11px;" }, [`${formatProfileTag(acc.profileId)} (${acc.profileId}) \xB7 ${String(acc.platform || "xiaohongshu")}`])
|
|
1922
1939
|
]),
|
|
1923
1940
|
createEl("span", {
|
|
1924
1941
|
className: `status-badge ${statusClass}`
|
|
@@ -1961,10 +1978,13 @@ function renderSetupWizard(root, ctx2) {
|
|
|
1961
1978
|
await refreshAccounts();
|
|
1962
1979
|
setupStatusText.textContent = `\u8D26\u53F7 ${profileId} \u5DF2\u521B\u5EFA\uFF0C\u7B49\u5F85\u767B\u5F55...`;
|
|
1963
1980
|
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
1981
|
+
const idleTimeout = String(ctx2.api?.settings?.idleTimeout || "30m").trim() || "30m";
|
|
1964
1982
|
const loginArgs = [
|
|
1965
1983
|
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
1966
1984
|
"login-profile",
|
|
1967
1985
|
profileId,
|
|
1986
|
+
"--idle-timeout",
|
|
1987
|
+
idleTimeout,
|
|
1968
1988
|
"--wait-sync",
|
|
1969
1989
|
"false",
|
|
1970
1990
|
"--timeout-sec",
|
|
@@ -2025,6 +2045,7 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2025
2045
|
"sync",
|
|
2026
2046
|
id,
|
|
2027
2047
|
"--pending-while-login",
|
|
2048
|
+
"--resolve-alias",
|
|
2028
2049
|
"--json"
|
|
2029
2050
|
],
|
|
2030
2051
|
timeoutMs: 2e4
|
|
@@ -2081,12 +2102,12 @@ function renderSetupWizard(root, ctx2) {
|
|
|
2081
2102
|
repairCoreBtn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2082
2103
|
repairCore2Btn.onclick = () => void runRepair("\u4FEE\u590D\u6838\u5FC3\u670D\u52A1", repairCoreServices);
|
|
2083
2104
|
repairCamoBtn.onclick = () => void runRepair("\u4FEE\u590D Camo CLI", repairCoreServices);
|
|
2084
|
-
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D
|
|
2105
|
+
repairRuntimeBtn.onclick = () => void runRepair("\u4FEE\u590D\u6D4F\u89C8\u5668\u5185\u6838", () => repairInstall({ browser: true }));
|
|
2085
2106
|
repairGeoipBtn.onclick = () => void runRepair("\u5B89\u88C5 GeoIP", () => repairInstall({ geoip: true }));
|
|
2086
2107
|
addAccountBtn.onclick = addAccount;
|
|
2087
2108
|
enterMainBtn.onclick = () => {
|
|
2088
2109
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2089
|
-
ctx2.setActiveTab("
|
|
2110
|
+
ctx2.setActiveTab("tasks");
|
|
2090
2111
|
}
|
|
2091
2112
|
};
|
|
2092
2113
|
void tickEnvironment();
|
|
@@ -2182,6 +2203,21 @@ function parseTaskRows(payload) {
|
|
|
2182
2203
|
runHistory: parseRunHistory(row?.runHistory)
|
|
2183
2204
|
})).filter((row) => row.id);
|
|
2184
2205
|
}
|
|
2206
|
+
function inferUiScheduleEditorState(task, nowMs = Date.now()) {
|
|
2207
|
+
const scheduleType = normalizeScheduleType(task?.scheduleType);
|
|
2208
|
+
if (scheduleType === "interval") {
|
|
2209
|
+
return { mode: "periodic", periodicType: "interval" };
|
|
2210
|
+
}
|
|
2211
|
+
if (scheduleType === "daily" || scheduleType === "weekly") {
|
|
2212
|
+
return { mode: "periodic", periodicType: scheduleType };
|
|
2213
|
+
}
|
|
2214
|
+
const runAtText = String(task?.runAt || "").trim();
|
|
2215
|
+
const runAtMs = Date.parse(runAtText);
|
|
2216
|
+
if (Number.isFinite(runAtMs) && runAtMs > nowMs + 6e4) {
|
|
2217
|
+
return { mode: "scheduled", periodicType: "interval" };
|
|
2218
|
+
}
|
|
2219
|
+
return { mode: "immediate", periodicType: "interval" };
|
|
2220
|
+
}
|
|
2185
2221
|
function getTasksForPlatform(platform) {
|
|
2186
2222
|
const p = platform;
|
|
2187
2223
|
return PLATFORM_TASKS[p] || [];
|
|
@@ -2208,7 +2244,8 @@ var DEFAULT_FORM = {
|
|
|
2208
2244
|
collectBody: true,
|
|
2209
2245
|
doLikes: false,
|
|
2210
2246
|
likeKeywords: "",
|
|
2211
|
-
|
|
2247
|
+
scheduleMode: "immediate",
|
|
2248
|
+
periodicType: "interval",
|
|
2212
2249
|
intervalMinutes: 30,
|
|
2213
2250
|
runAt: null,
|
|
2214
2251
|
maxRuns: null
|
|
@@ -2217,19 +2254,6 @@ function parseSortableTime(value) {
|
|
|
2217
2254
|
const ts = Date.parse(String(value || ""));
|
|
2218
2255
|
return Number.isFinite(ts) ? ts : 0;
|
|
2219
2256
|
}
|
|
2220
|
-
function isKeywordRequired(taskType) {
|
|
2221
|
-
return taskType === "xhs-unified" || taskType === "weibo-search" || taskType === "1688-search";
|
|
2222
|
-
}
|
|
2223
|
-
function commandTypeToWeiboTaskType(commandType) {
|
|
2224
|
-
if (commandType === "weibo-search") return "search";
|
|
2225
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
2226
|
-
return "timeline";
|
|
2227
|
-
}
|
|
2228
|
-
function fallbackTaskName(data) {
|
|
2229
|
-
const keyword = String(data.keyword || "").trim();
|
|
2230
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2231
|
-
return keyword ? `${data.taskType}-${keyword}` : `${data.taskType}-${stamp}`;
|
|
2232
|
-
}
|
|
2233
2257
|
function renderTasksPanel(root, ctx2) {
|
|
2234
2258
|
root.innerHTML = "";
|
|
2235
2259
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -2284,8 +2308,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2284
2308
|
<input id="task-target" type="number" min="1" value="50" style="width: 80px;" />
|
|
2285
2309
|
</div>
|
|
2286
2310
|
<div>
|
|
2287
|
-
<label>Profile</label>
|
|
2288
|
-
<input id="task-profile" placeholder="
|
|
2311
|
+
<label>Profile\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u9009\uFF09</label>
|
|
2312
|
+
<input id="task-profile" placeholder="\u7559\u7A7A\u81EA\u52A8\u9009\u62E9\u8BE5\u5E73\u53F0\u6709\u6548\u8D26\u53F7" style="width: 220px;" />
|
|
2313
|
+
<div id="task-profile-hint" class="muted" style="font-size:11px; margin-top:2px;">\u63A8\u8350: -</div>
|
|
2289
2314
|
</div>
|
|
2290
2315
|
<div>
|
|
2291
2316
|
<label>\u73AF\u5883</label>
|
|
@@ -2323,14 +2348,20 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2323
2348
|
<div style="font-size:12px; color:var(--text-secondary); margin-bottom:var(--gap-sm);">\u8C03\u5EA6\u8BBE\u7F6E\uFF08\u53EF\u9009\uFF09</div>
|
|
2324
2349
|
<div class="row">
|
|
2325
2350
|
<div>
|
|
2326
|
-
<select id="task-schedule-type" style="width:
|
|
2327
|
-
<option value="
|
|
2328
|
-
<option value="
|
|
2351
|
+
<select id="task-schedule-type" style="width: 140px;">
|
|
2352
|
+
<option value="immediate">\u9A6C\u4E0A\u6267\u884C\uFF08\u4EC5\u4E00\u6B21\uFF09</option>
|
|
2353
|
+
<option value="periodic">\u5468\u671F\u4EFB\u52A1</option>
|
|
2354
|
+
<option value="scheduled">\u5B9A\u65F6\u4EFB\u52A1</option>
|
|
2355
|
+
</select>
|
|
2356
|
+
</div>
|
|
2357
|
+
<div id="task-periodic-type-wrap" style="display:none;">
|
|
2358
|
+
<select id="task-periodic-type" style="width: 100px;">
|
|
2359
|
+
<option value="interval">\u6309\u95F4\u9694</option>
|
|
2329
2360
|
<option value="daily">\u6BCF\u5929</option>
|
|
2330
2361
|
<option value="weekly">\u6BCF\u5468</option>
|
|
2331
2362
|
</select>
|
|
2332
2363
|
</div>
|
|
2333
|
-
<div id="task-interval-wrap">
|
|
2364
|
+
<div id="task-interval-wrap" style="display:none;">
|
|
2334
2365
|
<input id="task-interval" type="number" min="1" value="30" style="width: 70px;" />
|
|
2335
2366
|
<span style="font-size:11px;color:var(--text-tertiary);">\u5206\u949F</span>
|
|
2336
2367
|
</div>
|
|
@@ -2347,7 +2378,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2347
2378
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
2348
2379
|
<button id="task-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
2349
2380
|
<button id="task-run-btn" class="primary" style="flex:1;">\u4FDD\u5B58\u5E76\u6267\u884C</button>
|
|
2350
|
-
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\
|
|
2381
|
+
<button id="task-run-ephemeral-btn" class="secondary" style="flex:1;">\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
2351
2382
|
<button id="task-reset-btn" class="secondary" style="flex:0.6;">\u91CD\u7F6E</button>
|
|
2352
2383
|
</div>
|
|
2353
2384
|
`;
|
|
@@ -2377,15 +2408,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2377
2408
|
root.appendChild(mainGrid);
|
|
2378
2409
|
const recentCard = createEl("div", { className: "bento-cell", style: "margin-top: var(--gap);" });
|
|
2379
2410
|
recentCard.innerHTML = `
|
|
2380
|
-
<div class="bento-title">\
|
|
2411
|
+
<div class="bento-title">\u5DF2\u4FDD\u5B58\u4EFB\u52A1\u5217\u8868</div>
|
|
2381
2412
|
<div class="row" style="margin-bottom: var(--gap-sm);">
|
|
2382
2413
|
<select id="task-history-select" style="min-width: 320px;">
|
|
2383
2414
|
<option value="">\u9009\u62E9\u5386\u53F2\u4EFB\u52A1...</option>
|
|
2384
2415
|
</select>
|
|
2385
2416
|
<button id="task-history-edit-btn" class="secondary">\u8F7D\u5165\u7F16\u8F91</button>
|
|
2386
2417
|
<button id="task-history-clone-btn" class="secondary">\u8F7D\u5165\u53E6\u5B58</button>
|
|
2418
|
+
<button id="task-history-run-btn">\u7ACB\u5373\u6267\u884C</button>
|
|
2387
2419
|
<button id="task-history-refresh-btn" class="secondary">\u5237\u65B0</button>
|
|
2388
2420
|
</div>
|
|
2421
|
+
<div class="muted" style="font-size:12px; margin-bottom:6px;">\u53CC\u51FB\u5217\u8868\u9879\u53EF\u76F4\u63A5\u5207\u6362\u4E3A\u5F53\u524D\u4EFB\u52A1\u3002</div>
|
|
2389
2422
|
<div id="recent-tasks-list"></div>
|
|
2390
2423
|
`;
|
|
2391
2424
|
root.appendChild(recentCard);
|
|
@@ -2396,6 +2429,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2396
2429
|
const keywordInput = formCard.querySelector("#task-keyword");
|
|
2397
2430
|
const targetInput = formCard.querySelector("#task-target");
|
|
2398
2431
|
const profileInput = formCard.querySelector("#task-profile");
|
|
2432
|
+
const profileHint = formCard.querySelector("#task-profile-hint");
|
|
2399
2433
|
const envSelect = formCard.querySelector("#task-env");
|
|
2400
2434
|
const userIdWrap = formCard.querySelector("#task-user-id-wrap");
|
|
2401
2435
|
const userIdInput = formCard.querySelector("#task-user-id");
|
|
@@ -2404,6 +2438,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2404
2438
|
const likesInput = formCard.querySelector("#task-likes");
|
|
2405
2439
|
const likeKeywordsInput = formCard.querySelector("#task-like-keywords");
|
|
2406
2440
|
const scheduleTypeSelect = formCard.querySelector("#task-schedule-type");
|
|
2441
|
+
const periodicTypeWrap = formCard.querySelector("#task-periodic-type-wrap");
|
|
2442
|
+
const periodicTypeSelect = formCard.querySelector("#task-periodic-type");
|
|
2407
2443
|
const intervalInput = formCard.querySelector("#task-interval");
|
|
2408
2444
|
const intervalWrap = formCard.querySelector("#task-interval-wrap");
|
|
2409
2445
|
const runAtInput = formCard.querySelector("#task-runat");
|
|
@@ -2419,22 +2455,62 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2419
2455
|
const historySelect = recentCard.querySelector("#task-history-select");
|
|
2420
2456
|
const historyEditBtn = recentCard.querySelector("#task-history-edit-btn");
|
|
2421
2457
|
const historyCloneBtn = recentCard.querySelector("#task-history-clone-btn");
|
|
2458
|
+
const historyRunBtn = recentCard.querySelector("#task-history-run-btn");
|
|
2422
2459
|
const historyRefreshBtn = recentCard.querySelector("#task-history-refresh-btn");
|
|
2423
2460
|
const recentTasksList = recentCard.querySelector("#recent-tasks-list");
|
|
2424
2461
|
const statRunning = statsCard.querySelector("#stat-running");
|
|
2425
2462
|
const statToday = statsCard.querySelector("#stat-today");
|
|
2426
2463
|
const statSaved = statsCard.querySelector("#stat-saved");
|
|
2427
2464
|
let tasks = [];
|
|
2465
|
+
let accountRows = [];
|
|
2428
2466
|
const activeRunIds = /* @__PURE__ */ new Set();
|
|
2429
2467
|
let unsubscribeActiveRuns = null;
|
|
2430
2468
|
const joinPath2 = (...parts) => {
|
|
2431
2469
|
if (typeof ctx2?.api?.pathJoin === "function") return ctx2.api.pathJoin(...parts);
|
|
2432
2470
|
return parts.filter(Boolean).join("/");
|
|
2433
2471
|
};
|
|
2434
|
-
const scheduleScript = joinPath2("apps", "webauto", "entry", "schedule.mjs");
|
|
2435
2472
|
const quotaScript = joinPath2("apps", "webauto", "entry", "lib", "quota-status.mjs");
|
|
2436
|
-
|
|
2437
|
-
|
|
2473
|
+
function normalizePlatform2(value) {
|
|
2474
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
2475
|
+
if (raw === "weibo") return "weibo";
|
|
2476
|
+
if (raw === "1688") return "1688";
|
|
2477
|
+
return "xiaohongshu";
|
|
2478
|
+
}
|
|
2479
|
+
function platformToAccountPlatform(value) {
|
|
2480
|
+
return value === "xiaohongshu" ? "xiaohongshu" : value;
|
|
2481
|
+
}
|
|
2482
|
+
async function refreshPlatformAccountRows(platform) {
|
|
2483
|
+
try {
|
|
2484
|
+
accountRows = await listAccountProfiles(ctx2.api, { platform: platformToAccountPlatform(platform) });
|
|
2485
|
+
} catch {
|
|
2486
|
+
accountRows = [];
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function getRecommendedProfile(platform) {
|
|
2490
|
+
const candidates = accountRows.filter((row) => row.valid).sort((a, b) => {
|
|
2491
|
+
const ta = Date.parse(String(a.updatedAt || "")) || 0;
|
|
2492
|
+
const tb = Date.parse(String(b.updatedAt || "")) || 0;
|
|
2493
|
+
if (tb !== ta) return tb - ta;
|
|
2494
|
+
return String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
2495
|
+
});
|
|
2496
|
+
return candidates[0] || null;
|
|
2497
|
+
}
|
|
2498
|
+
function updateProfileHint(platform) {
|
|
2499
|
+
const recommended = getRecommendedProfile(platform);
|
|
2500
|
+
if (!recommended) {
|
|
2501
|
+
profileHint.textContent = `\u63A8\u8350: \u5F53\u524D\u5E73\u53F0(${platform})\u65E0\u6709\u6548\u8D26\u53F7\uFF0C\u8BF7\u5148\u5230\u8D26\u53F7\u9875\u767B\u5F55`;
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const label = recommended.alias || recommended.name || recommended.profileId;
|
|
2505
|
+
profileHint.textContent = `\u63A8\u8350: ${label} (${recommended.profileId})`;
|
|
2506
|
+
}
|
|
2507
|
+
function maybeAutofillProfile(platform) {
|
|
2508
|
+
const current = String(profileInput.value || "").trim();
|
|
2509
|
+
if (current) return;
|
|
2510
|
+
const recommended = getRecommendedProfile(platform);
|
|
2511
|
+
if (!recommended) return;
|
|
2512
|
+
profileInput.value = recommended.profileId;
|
|
2513
|
+
}
|
|
2438
2514
|
function getTaskById(taskId) {
|
|
2439
2515
|
const id = String(taskId || "").trim();
|
|
2440
2516
|
if (!id) return null;
|
|
@@ -2452,13 +2528,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2452
2528
|
formTitle.textContent = "\u65B0\u5EFA\u4EFB\u52A1";
|
|
2453
2529
|
}
|
|
2454
2530
|
function updateTaskTypeOptions(preferredType = "") {
|
|
2455
|
-
const platform = platformSelect.value;
|
|
2531
|
+
const platform = normalizePlatform2(platformSelect.value);
|
|
2456
2532
|
const options = getTasksForPlatform(platform);
|
|
2457
2533
|
taskTypeSelect.innerHTML = options.map((item) => `<option value="${item.type}">${item.icon} ${item.label}</option>`).join("");
|
|
2458
2534
|
const target = String(preferredType || "").trim();
|
|
2459
2535
|
const matched = options.find((item) => item.type === target);
|
|
2460
2536
|
taskTypeSelect.value = matched?.type || options[0]?.type || "";
|
|
2461
2537
|
updatePlatformFields();
|
|
2538
|
+
void refreshPlatformAccountRows(platform).then(() => {
|
|
2539
|
+
updateProfileHint(platform);
|
|
2540
|
+
maybeAutofillProfile(platform);
|
|
2541
|
+
});
|
|
2462
2542
|
}
|
|
2463
2543
|
function updatePlatformFields() {
|
|
2464
2544
|
const taskType = String(taskTypeSelect.value || "").trim();
|
|
@@ -2466,9 +2546,17 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2466
2546
|
userIdWrap.style.display = isWeiboMonitor ? "" : "none";
|
|
2467
2547
|
}
|
|
2468
2548
|
function updateScheduleVisibility() {
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2549
|
+
const mode = String(scheduleTypeSelect.value || "immediate").trim();
|
|
2550
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
2551
|
+
const periodic = mode === "periodic";
|
|
2552
|
+
const scheduled = mode === "scheduled";
|
|
2553
|
+
periodicTypeWrap.style.display = periodic ? "inline-flex" : "none";
|
|
2554
|
+
intervalWrap.style.display = periodic && periodicType === "interval" ? "inline-flex" : "none";
|
|
2555
|
+
runAtWrap.style.display = scheduled || periodic && periodicType !== "interval" ? "inline-flex" : "none";
|
|
2556
|
+
maxRunsInput.disabled = mode === "immediate";
|
|
2557
|
+
if (mode === "immediate") {
|
|
2558
|
+
maxRunsInput.value = "";
|
|
2559
|
+
}
|
|
2472
2560
|
}
|
|
2473
2561
|
function updateLikeKeywordsState() {
|
|
2474
2562
|
likeKeywordsInput.disabled = !likesInput.checked;
|
|
@@ -2476,6 +2564,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2476
2564
|
function collectFormData() {
|
|
2477
2565
|
const maxRunsRaw = String(maxRunsInput.value || "").trim();
|
|
2478
2566
|
const maxRunsNum = maxRunsRaw ? Number(maxRunsRaw) : 0;
|
|
2567
|
+
const scheduleMode = scheduleTypeSelect.value;
|
|
2568
|
+
const periodicType = periodicTypeSelect.value;
|
|
2569
|
+
const runAtText = String(runAtInput.value || "").trim();
|
|
2479
2570
|
return {
|
|
2480
2571
|
id: String(editingIdInput.value || "").trim() || void 0,
|
|
2481
2572
|
name: String(nameInput.value || "").trim(),
|
|
@@ -2491,9 +2582,10 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2491
2582
|
collectBody: bodyInput.checked,
|
|
2492
2583
|
doLikes: likesInput.checked,
|
|
2493
2584
|
likeKeywords: String(likeKeywordsInput.value || "").trim(),
|
|
2494
|
-
|
|
2585
|
+
scheduleMode,
|
|
2586
|
+
periodicType,
|
|
2495
2587
|
intervalMinutes: Math.max(1, Number(intervalInput.value || 30) || 30),
|
|
2496
|
-
runAt: toIsoOrNull(
|
|
2588
|
+
runAt: toIsoOrNull(runAtText),
|
|
2497
2589
|
maxRuns: Number.isFinite(maxRunsNum) && maxRunsNum > 0 ? Math.max(1, Math.floor(maxRunsNum)) : null
|
|
2498
2590
|
};
|
|
2499
2591
|
}
|
|
@@ -2513,7 +2605,9 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2513
2605
|
bodyInput.checked = task.commandArgv?.["fetch-body"] !== false;
|
|
2514
2606
|
likesInput.checked = task.commandArgv?.["do-likes"] === true;
|
|
2515
2607
|
likeKeywordsInput.value = String(task.commandArgv?.["like-keywords"] || "").trim();
|
|
2516
|
-
|
|
2608
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
2609
|
+
scheduleTypeSelect.value = uiSchedule.mode;
|
|
2610
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
2517
2611
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
2518
2612
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
2519
2613
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -2536,7 +2630,8 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2536
2630
|
bodyInput.checked = DEFAULT_FORM.collectBody;
|
|
2537
2631
|
likesInput.checked = DEFAULT_FORM.doLikes;
|
|
2538
2632
|
likeKeywordsInput.value = DEFAULT_FORM.likeKeywords;
|
|
2539
|
-
scheduleTypeSelect.value = DEFAULT_FORM.
|
|
2633
|
+
scheduleTypeSelect.value = DEFAULT_FORM.scheduleMode;
|
|
2634
|
+
periodicTypeSelect.value = DEFAULT_FORM.periodicType;
|
|
2540
2635
|
intervalInput.value = String(DEFAULT_FORM.intervalMinutes);
|
|
2541
2636
|
runAtInput.value = "";
|
|
2542
2637
|
maxRunsInput.value = "";
|
|
@@ -2570,19 +2665,29 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2570
2665
|
}
|
|
2571
2666
|
}
|
|
2572
2667
|
function renderRecentTasks() {
|
|
2573
|
-
const rows = sortedTasksByRecent()
|
|
2668
|
+
const rows = sortedTasksByRecent();
|
|
2574
2669
|
if (rows.length === 0) {
|
|
2575
2670
|
recentTasksList.innerHTML = '<div class="muted" style="font-size:12px;">\u6682\u65E0\u4EFB\u52A1</div>';
|
|
2576
2671
|
return;
|
|
2577
2672
|
}
|
|
2578
2673
|
recentTasksList.innerHTML = rows.map((task) => `
|
|
2579
|
-
<div class="task-row" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;">
|
|
2674
|
+
<div class="task-row task-item" data-id="${task.id}" style="display:flex;gap:var(--gap-sm);padding:var(--gap-xs)0;border-bottom:1px solid var(--border-subtle);align-items:center;cursor:pointer;">
|
|
2580
2675
|
<span style="flex:1;font-size:12px;">${task.name || task.id}</span>
|
|
2581
2676
|
<span style="font-size:11px;color:var(--text-tertiary);">${task.commandType}</span>
|
|
2582
2677
|
<span style="font-size:11px;color:${task.enabled ? "var(--accent-success)" : "var(--text-muted)"};">${task.enabled ? "\u542F\u7528" : "\u7981\u7528"}</span>
|
|
2583
2678
|
<button class="secondary edit-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7F16\u8F91</button>
|
|
2679
|
+
<button class="run-task-btn" data-id="${task.id}" style="padding:2px 6px;font-size:10px;height:auto;">\u7ACB\u5373\u6267\u884C</button>
|
|
2584
2680
|
</div>
|
|
2585
2681
|
`).join("");
|
|
2682
|
+
recentTasksList.querySelectorAll(".task-item").forEach((item) => {
|
|
2683
|
+
item.addEventListener("dblclick", () => {
|
|
2684
|
+
const taskId = item.dataset.id || "";
|
|
2685
|
+
const task = getTaskById(taskId);
|
|
2686
|
+
if (!task) return;
|
|
2687
|
+
historySelect.value = task.id;
|
|
2688
|
+
applyTaskToForm(task, "edit");
|
|
2689
|
+
});
|
|
2690
|
+
});
|
|
2586
2691
|
recentTasksList.querySelectorAll(".edit-task-btn").forEach((btn) => {
|
|
2587
2692
|
btn.addEventListener("click", () => {
|
|
2588
2693
|
const taskId = btn.dataset.id || "";
|
|
@@ -2592,6 +2697,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2592
2697
|
applyTaskToForm(task, "edit");
|
|
2593
2698
|
});
|
|
2594
2699
|
});
|
|
2700
|
+
recentTasksList.querySelectorAll(".run-task-btn").forEach((btn) => {
|
|
2701
|
+
btn.addEventListener("click", () => {
|
|
2702
|
+
const taskId = btn.dataset.id || "";
|
|
2703
|
+
const task = getTaskById(taskId);
|
|
2704
|
+
if (!task) return;
|
|
2705
|
+
void runTaskImmediately(task);
|
|
2706
|
+
});
|
|
2707
|
+
});
|
|
2595
2708
|
}
|
|
2596
2709
|
function updateStats() {
|
|
2597
2710
|
statSaved.textContent = String(tasks.length);
|
|
@@ -2599,21 +2712,27 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2599
2712
|
const totalRunCount = tasks.reduce((sum, row) => sum + (Number(row.runCount) || 0), 0);
|
|
2600
2713
|
statToday.textContent = String(totalRunCount);
|
|
2601
2714
|
}
|
|
2602
|
-
async function
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
timeoutMs
|
|
2608
|
-
});
|
|
2715
|
+
async function invokeSchedule(input) {
|
|
2716
|
+
if (typeof ctx2.api?.scheduleInvoke !== "function") {
|
|
2717
|
+
throw new Error("scheduleInvoke unavailable");
|
|
2718
|
+
}
|
|
2719
|
+
const ret = await ctx2.api.scheduleInvoke(input);
|
|
2609
2720
|
if (!ret?.ok) {
|
|
2610
|
-
const reason = String(ret?.error ||
|
|
2611
|
-
throw new Error(reason || "command failed");
|
|
2721
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
2722
|
+
throw new Error(reason || "schedule command failed");
|
|
2612
2723
|
}
|
|
2613
|
-
return ret
|
|
2724
|
+
return ret?.json ?? ret;
|
|
2614
2725
|
}
|
|
2615
|
-
async function
|
|
2616
|
-
|
|
2726
|
+
async function invokeTaskRunEphemeral(input) {
|
|
2727
|
+
if (typeof ctx2.api?.taskRunEphemeral !== "function") {
|
|
2728
|
+
throw new Error("taskRunEphemeral unavailable");
|
|
2729
|
+
}
|
|
2730
|
+
const ret = await ctx2.api.taskRunEphemeral(input);
|
|
2731
|
+
if (!ret?.ok) {
|
|
2732
|
+
const reason = String(ret?.error || "run ephemeral failed").trim();
|
|
2733
|
+
throw new Error(reason || "run ephemeral failed");
|
|
2734
|
+
}
|
|
2735
|
+
return ret;
|
|
2617
2736
|
}
|
|
2618
2737
|
async function loadQuotaStatus() {
|
|
2619
2738
|
try {
|
|
@@ -2642,7 +2761,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2642
2761
|
}
|
|
2643
2762
|
async function loadTasks() {
|
|
2644
2763
|
try {
|
|
2645
|
-
const out = await
|
|
2764
|
+
const out = await invokeSchedule({ action: "list" });
|
|
2646
2765
|
tasks = parseTaskRows(out);
|
|
2647
2766
|
renderHistorySelect();
|
|
2648
2767
|
renderRecentTasks();
|
|
@@ -2653,7 +2772,6 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2653
2772
|
}
|
|
2654
2773
|
function buildCommandArgv(data) {
|
|
2655
2774
|
const argv = {
|
|
2656
|
-
profile: data.profileId,
|
|
2657
2775
|
keyword: data.keyword,
|
|
2658
2776
|
"max-notes": data.targetCount,
|
|
2659
2777
|
target: data.targetCount,
|
|
@@ -2663,36 +2781,70 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2663
2781
|
"do-likes": data.doLikes,
|
|
2664
2782
|
"like-keywords": data.likeKeywords
|
|
2665
2783
|
};
|
|
2784
|
+
const profileId = String(data.profileId || "").trim();
|
|
2785
|
+
if (profileId) argv.profile = profileId;
|
|
2666
2786
|
if (String(data.taskType || "").startsWith("weibo-")) {
|
|
2667
|
-
argv["task-type"] = commandTypeToWeiboTaskType(data.taskType);
|
|
2668
2787
|
if (data.userId) argv["user-id"] = data.userId;
|
|
2669
2788
|
}
|
|
2670
2789
|
return argv;
|
|
2671
2790
|
}
|
|
2672
|
-
function
|
|
2673
|
-
if (
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
const args = data.id ? ["update", data.id] : ["add"];
|
|
2681
|
-
args.push("--name", data.name || fallbackTaskName(data));
|
|
2682
|
-
args.push("--enabled", String(data.enabled));
|
|
2683
|
-
args.push("--command-type", data.taskType || "xhs-unified");
|
|
2684
|
-
args.push("--schedule-type", data.scheduleType);
|
|
2685
|
-
if (data.scheduleType === "interval") {
|
|
2686
|
-
args.push("--interval-minutes", String(data.intervalMinutes));
|
|
2687
|
-
} else {
|
|
2688
|
-
args.push("--run-at", String(data.runAt || ""));
|
|
2791
|
+
function resolveSchedule(data) {
|
|
2792
|
+
if (data.scheduleMode === "immediate") {
|
|
2793
|
+
return {
|
|
2794
|
+
scheduleType: "once",
|
|
2795
|
+
intervalMinutes: data.intervalMinutes,
|
|
2796
|
+
runAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2797
|
+
maxRuns: 1
|
|
2798
|
+
};
|
|
2689
2799
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2800
|
+
if (data.scheduleMode === "scheduled") {
|
|
2801
|
+
return {
|
|
2802
|
+
scheduleType: "once",
|
|
2803
|
+
intervalMinutes: data.intervalMinutes,
|
|
2804
|
+
runAt: data.runAt,
|
|
2805
|
+
maxRuns: 1
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
const periodicType = data.periodicType;
|
|
2809
|
+
if (periodicType === "daily" || periodicType === "weekly") {
|
|
2810
|
+
return {
|
|
2811
|
+
scheduleType: periodicType,
|
|
2812
|
+
intervalMinutes: data.intervalMinutes,
|
|
2813
|
+
runAt: data.runAt,
|
|
2814
|
+
maxRuns: data.maxRuns
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
return {
|
|
2818
|
+
scheduleType: "interval",
|
|
2819
|
+
intervalMinutes: data.intervalMinutes,
|
|
2820
|
+
runAt: null,
|
|
2821
|
+
maxRuns: data.maxRuns
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
function toSchedulePayload(data) {
|
|
2825
|
+
const schedule = resolveSchedule(data);
|
|
2826
|
+
return {
|
|
2827
|
+
id: data.id || "",
|
|
2828
|
+
name: data.name || "",
|
|
2829
|
+
enabled: data.enabled,
|
|
2830
|
+
commandType: data.taskType || "xhs-unified",
|
|
2831
|
+
scheduleType: schedule.scheduleType,
|
|
2832
|
+
intervalMinutes: schedule.intervalMinutes,
|
|
2833
|
+
runAt: schedule.runAt,
|
|
2834
|
+
maxRuns: schedule.maxRuns,
|
|
2835
|
+
argv: buildCommandArgv(data)
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
function taskToRunMeta(task) {
|
|
2839
|
+
return {
|
|
2840
|
+
taskType: String(task.commandType || "xhs-unified").trim() || "xhs-unified",
|
|
2841
|
+
profileId: String(task.commandArgv?.profile || task.commandArgv?.profileId || "").trim(),
|
|
2842
|
+
keyword: String(task.commandArgv?.keyword || task.commandArgv?.k || "").trim(),
|
|
2843
|
+
targetCount: Math.max(1, Number(task.commandArgv?.["max-notes"] ?? task.commandArgv?.target ?? 50) || 50)
|
|
2844
|
+
};
|
|
2693
2845
|
}
|
|
2694
2846
|
async function runSavedTask(taskId, data) {
|
|
2695
|
-
const out = await
|
|
2847
|
+
const out = await invokeSchedule({ action: "run", taskId, timeoutMs: 0 });
|
|
2696
2848
|
const runId = String(
|
|
2697
2849
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
2698
2850
|
).trim();
|
|
@@ -2708,23 +2860,30 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2708
2860
|
target: data.targetCount,
|
|
2709
2861
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2710
2862
|
};
|
|
2863
|
+
ctx2.activeRunId = runId || null;
|
|
2711
2864
|
}
|
|
2712
2865
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2713
2866
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
2714
2867
|
}
|
|
2715
2868
|
}
|
|
2869
|
+
async function runTaskImmediately(task) {
|
|
2870
|
+
const taskId = String(task.id || "").trim();
|
|
2871
|
+
if (!taskId) return;
|
|
2872
|
+
historySelect.value = taskId;
|
|
2873
|
+
applyTaskToForm(task, "edit");
|
|
2874
|
+
await runSavedTask(taskId, taskToRunMeta(task));
|
|
2875
|
+
}
|
|
2716
2876
|
async function saveTask(runImmediately = false) {
|
|
2717
2877
|
const data = collectFormData();
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
alert(invalidReason);
|
|
2878
|
+
if (runImmediately && data.scheduleMode === "immediate") {
|
|
2879
|
+
await runWithoutSave();
|
|
2721
2880
|
return;
|
|
2722
2881
|
}
|
|
2723
2882
|
saveBtn.disabled = true;
|
|
2724
2883
|
runBtn.disabled = true;
|
|
2725
2884
|
runEphemeralBtn.disabled = true;
|
|
2726
2885
|
try {
|
|
2727
|
-
const out = await
|
|
2886
|
+
const out = await invokeSchedule({ action: "save", payload: toSchedulePayload(data) });
|
|
2728
2887
|
const taskId = String(out?.task?.id || data.id || "").trim();
|
|
2729
2888
|
if (!taskId) {
|
|
2730
2889
|
throw new Error("task id missing after save");
|
|
@@ -2734,7 +2893,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2734
2893
|
await loadTasks();
|
|
2735
2894
|
historySelect.value = taskId;
|
|
2736
2895
|
if (runImmediately) {
|
|
2737
|
-
|
|
2896
|
+
const resolvedProfile = String(out?.task?.commandArgv?.profile || data.profileId || "").trim();
|
|
2897
|
+
await runSavedTask(taskId, {
|
|
2898
|
+
taskType: data.taskType,
|
|
2899
|
+
profileId: resolvedProfile,
|
|
2900
|
+
keyword: data.keyword,
|
|
2901
|
+
targetCount: data.targetCount
|
|
2902
|
+
});
|
|
2738
2903
|
} else {
|
|
2739
2904
|
alert("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
|
|
2740
2905
|
}
|
|
@@ -2746,83 +2911,13 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2746
2911
|
runEphemeralBtn.disabled = false;
|
|
2747
2912
|
}
|
|
2748
2913
|
}
|
|
2749
|
-
function buildEphemeralRunSpec(data) {
|
|
2750
|
-
if (!data.profileId) return null;
|
|
2751
|
-
if (data.taskType === "xhs-unified") {
|
|
2752
|
-
if (!data.keyword) return null;
|
|
2753
|
-
return {
|
|
2754
|
-
title: `xhs: ${data.keyword}`,
|
|
2755
|
-
groupKey: "xhs-unified",
|
|
2756
|
-
args: [
|
|
2757
|
-
xhsScript,
|
|
2758
|
-
"--profile",
|
|
2759
|
-
data.profileId,
|
|
2760
|
-
"--keyword",
|
|
2761
|
-
data.keyword,
|
|
2762
|
-
"--target",
|
|
2763
|
-
String(data.targetCount),
|
|
2764
|
-
"--max-notes",
|
|
2765
|
-
String(data.targetCount),
|
|
2766
|
-
"--env",
|
|
2767
|
-
data.env,
|
|
2768
|
-
"--do-comments",
|
|
2769
|
-
String(data.collectComments),
|
|
2770
|
-
"--fetch-body",
|
|
2771
|
-
String(data.collectBody),
|
|
2772
|
-
"--do-likes",
|
|
2773
|
-
String(data.doLikes),
|
|
2774
|
-
"--like-keywords",
|
|
2775
|
-
data.likeKeywords
|
|
2776
|
-
]
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
if (data.taskType === "weibo-search") {
|
|
2780
|
-
if (!data.keyword) return null;
|
|
2781
|
-
return {
|
|
2782
|
-
title: `weibo: ${data.keyword}`,
|
|
2783
|
-
groupKey: "weibo-search",
|
|
2784
|
-
args: [
|
|
2785
|
-
weiboScript,
|
|
2786
|
-
"search",
|
|
2787
|
-
"--profile",
|
|
2788
|
-
data.profileId,
|
|
2789
|
-
"--keyword",
|
|
2790
|
-
data.keyword,
|
|
2791
|
-
"--target",
|
|
2792
|
-
String(data.targetCount),
|
|
2793
|
-
"--env",
|
|
2794
|
-
data.env
|
|
2795
|
-
]
|
|
2796
|
-
};
|
|
2797
|
-
}
|
|
2798
|
-
return null;
|
|
2799
|
-
}
|
|
2800
2914
|
async function runWithoutSave() {
|
|
2801
2915
|
const data = collectFormData();
|
|
2802
|
-
if (!data.profileId) {
|
|
2803
|
-
alert("\u8BF7\u8F93\u5165 Profile ID");
|
|
2804
|
-
return;
|
|
2805
|
-
}
|
|
2806
|
-
if (isKeywordRequired(data.taskType) && !data.keyword) {
|
|
2807
|
-
alert("\u8BF7\u8F93\u5165\u5173\u952E\u8BCD");
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
if (data.taskType === "weibo-monitor" && !data.userId) {
|
|
2811
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
2812
|
-
return;
|
|
2813
|
-
}
|
|
2814
|
-
const spec = buildEphemeralRunSpec(data);
|
|
2815
|
-
if (!spec) {
|
|
2816
|
-
alert(`\u5F53\u524D\u4EFB\u52A1\u7C7B\u578B\u6682\u4E0D\u652F\u6301\u4EC5\u6267\u884C(\u4E0D\u4FDD\u5B58): ${data.taskType}`);
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
2916
|
runEphemeralBtn.disabled = true;
|
|
2820
2917
|
try {
|
|
2821
|
-
const ret = await
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
args: spec.args,
|
|
2825
|
-
groupKey: spec.groupKey
|
|
2918
|
+
const ret = await invokeTaskRunEphemeral({
|
|
2919
|
+
commandType: data.taskType,
|
|
2920
|
+
argv: buildCommandArgv(data)
|
|
2826
2921
|
});
|
|
2827
2922
|
const runId = String(ret?.runId || "").trim();
|
|
2828
2923
|
if (runId) {
|
|
@@ -2830,17 +2925,19 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2830
2925
|
updateStats();
|
|
2831
2926
|
}
|
|
2832
2927
|
if (typeof ctx2.setStatus === "function") {
|
|
2833
|
-
ctx2.setStatus(`started: ${
|
|
2928
|
+
ctx2.setStatus(`started: ${data.taskType}`);
|
|
2834
2929
|
}
|
|
2835
2930
|
if (data.taskType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
2931
|
+
const resolvedProfile = String(ret?.profile || data.profileId || "").trim();
|
|
2836
2932
|
ctx2.xhsCurrentRun = {
|
|
2837
2933
|
runId: runId || null,
|
|
2838
2934
|
taskId: null,
|
|
2839
|
-
profileId:
|
|
2935
|
+
profileId: resolvedProfile,
|
|
2840
2936
|
keyword: data.keyword,
|
|
2841
2937
|
target: data.targetCount,
|
|
2842
2938
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2843
2939
|
};
|
|
2940
|
+
ctx2.activeRunId = runId || null;
|
|
2844
2941
|
}
|
|
2845
2942
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2846
2943
|
ctx2.setActiveTab(data.taskType === "xhs-unified" ? "dashboard" : "scheduler");
|
|
@@ -2873,6 +2970,7 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2873
2970
|
});
|
|
2874
2971
|
taskTypeSelect.addEventListener("change", () => updatePlatformFields());
|
|
2875
2972
|
scheduleTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2973
|
+
periodicTypeSelect.addEventListener("change", () => updateScheduleVisibility());
|
|
2876
2974
|
likesInput.addEventListener("change", () => updateLikeKeywordsState());
|
|
2877
2975
|
saveBtn.addEventListener("click", () => {
|
|
2878
2976
|
void saveTask(false);
|
|
@@ -2906,6 +3004,14 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2906
3004
|
}
|
|
2907
3005
|
applyTaskToForm(task, "clone");
|
|
2908
3006
|
});
|
|
3007
|
+
historyRunBtn.addEventListener("click", () => {
|
|
3008
|
+
const task = selectedHistoryTask();
|
|
3009
|
+
if (!task) {
|
|
3010
|
+
alert("\u8BF7\u5148\u9009\u62E9\u5386\u53F2\u4EFB\u52A1");
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
void runTaskImmediately(task);
|
|
3014
|
+
});
|
|
2909
3015
|
gotoSchedulerBtn.addEventListener("click", () => {
|
|
2910
3016
|
if (typeof ctx2.setActiveTab === "function") {
|
|
2911
3017
|
ctx2.setActiveTab("scheduler");
|
|
@@ -2993,6 +3099,11 @@ function renderDashboard(root, ctx2) {
|
|
|
2993
3099
|
<div id="recent-errors-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u9519\u8BEF</div>
|
|
2994
3100
|
<ul id="recent-errors-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
2995
3101
|
</div>
|
|
3102
|
+
<div style="margin-top: 10px;">
|
|
3103
|
+
<label>\u70B9\u8D5E\u94FE\u63A5\uFF08\u6700\u591A 30 \u6761\uFF09</label>
|
|
3104
|
+
<div id="liked-links-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u70B9\u8D5E\u8BB0\u5F55</div>
|
|
3105
|
+
<ul id="liked-links-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
3106
|
+
</div>
|
|
2996
3107
|
`;
|
|
2997
3108
|
runSummaryGrid.appendChild(runSummaryCard);
|
|
2998
3109
|
root.appendChild(runSummaryGrid);
|
|
@@ -3087,6 +3198,8 @@ function renderDashboard(root, ctx2) {
|
|
|
3087
3198
|
const errorCountText = root.querySelector("#error-count-text");
|
|
3088
3199
|
const recentErrorsEmpty = root.querySelector("#recent-errors-empty");
|
|
3089
3200
|
const recentErrorsList = root.querySelector("#recent-errors-list");
|
|
3201
|
+
const likedLinksEmpty = root.querySelector("#liked-links-empty");
|
|
3202
|
+
const likedLinksList = root.querySelector("#liked-links-list");
|
|
3090
3203
|
const logsContainer = root.querySelector("#logs-container");
|
|
3091
3204
|
const toggleLogsBtn = root.querySelector("#toggle-logs-btn");
|
|
3092
3205
|
const pauseBtn = root.querySelector("#pause-btn");
|
|
@@ -3103,23 +3216,175 @@ function renderDashboard(root, ctx2) {
|
|
|
3103
3216
|
let startTime = Date.now();
|
|
3104
3217
|
let stoppedAt = null;
|
|
3105
3218
|
let elapsedTimer = null;
|
|
3219
|
+
let statePollTimer = null;
|
|
3106
3220
|
let unsubscribeState = null;
|
|
3107
3221
|
let unsubscribeCmd = null;
|
|
3108
|
-
|
|
3222
|
+
const contextRun = ctx2?.xhsCurrentRun && typeof ctx2.xhsCurrentRun === "object" ? ctx2.xhsCurrentRun : null;
|
|
3223
|
+
const contextStartedAtMs = Date.parse(String(contextRun?.startedAt || ""));
|
|
3224
|
+
let activeRunId = String(contextRun?.runId || ctx2?.activeRunId || "").trim();
|
|
3225
|
+
let activeProfileId = String(contextRun?.profileId || "").trim();
|
|
3109
3226
|
let activeStatus = "";
|
|
3110
3227
|
let errorCountTotal = 0;
|
|
3111
3228
|
const recentErrors = [];
|
|
3229
|
+
const likedLinks = /* @__PURE__ */ new Map();
|
|
3112
3230
|
const maxLogs = 500;
|
|
3113
3231
|
const maxRecentErrors = 8;
|
|
3114
|
-
const
|
|
3232
|
+
const maxLikedLinks = 30;
|
|
3233
|
+
const initialTaskId = String(contextRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3115
3234
|
if (initialTaskId) {
|
|
3116
3235
|
taskConfigId.textContent = initialTaskId;
|
|
3117
3236
|
}
|
|
3118
3237
|
const normalizeStatus = (value) => String(value || "").trim().toLowerCase();
|
|
3119
3238
|
const isRunningStatus = (value) => ["running", "queued", "pending", "starting"].includes(normalizeStatus(value));
|
|
3120
3239
|
const isTerminalStatus = (value) => ["completed", "done", "success", "succeeded", "failed", "error", "stopped", "canceled"].includes(normalizeStatus(value));
|
|
3240
|
+
const isXhsCommandTitle = (title) => {
|
|
3241
|
+
const normalized = String(title || "").trim().toLowerCase();
|
|
3242
|
+
if (!normalized) return false;
|
|
3243
|
+
return normalized.includes("xhs unified") || normalized.startsWith("xhs:") || normalized.startsWith("xhs unified:");
|
|
3244
|
+
};
|
|
3245
|
+
const hasRenderableValue = (value) => {
|
|
3246
|
+
const text = String(value ?? "").trim();
|
|
3247
|
+
return text.length > 0 && text !== "-";
|
|
3248
|
+
};
|
|
3249
|
+
if (contextRun) {
|
|
3250
|
+
if (hasRenderableValue(contextRun.keyword)) taskKeyword.textContent = String(contextRun.keyword);
|
|
3251
|
+
if (Number(contextRun.target) > 0) taskTarget.textContent = String(Number(contextRun.target));
|
|
3252
|
+
if (hasRenderableValue(contextRun.profileId)) {
|
|
3253
|
+
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3254
|
+
const profileId = String(contextRun.profileId);
|
|
3255
|
+
taskAccount.textContent = aliases[profileId] || profileId;
|
|
3256
|
+
activeProfileId = profileId;
|
|
3257
|
+
}
|
|
3258
|
+
if (hasRenderableValue(contextRun.taskId)) taskConfigId.textContent = String(contextRun.taskId);
|
|
3259
|
+
const startedAtTs = Date.parse(String(contextRun.startedAt || ""));
|
|
3260
|
+
if (Number.isFinite(startedAtTs) && startedAtTs > 0) {
|
|
3261
|
+
startTime = startedAtTs;
|
|
3262
|
+
updateElapsed();
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
function normalizeDetails(details) {
|
|
3266
|
+
if (details === void 0 || details === null) return null;
|
|
3267
|
+
try {
|
|
3268
|
+
const text = typeof details === "string" ? details : JSON.stringify(details, null, 2);
|
|
3269
|
+
const trimmed = String(text || "").trim();
|
|
3270
|
+
if (!trimmed) return null;
|
|
3271
|
+
return trimmed.length > 2e3 ? `${trimmed.slice(0, 2e3)}
|
|
3272
|
+
...` : trimmed;
|
|
3273
|
+
} catch {
|
|
3274
|
+
return String(details || "").trim() || null;
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
function normalizeNoteId(value) {
|
|
3278
|
+
const text = String(value || "").trim();
|
|
3279
|
+
if (!text) return null;
|
|
3280
|
+
if (/^[a-zA-Z0-9_-]{6,}$/.test(text)) return text;
|
|
3281
|
+
return null;
|
|
3282
|
+
}
|
|
3283
|
+
function normalizeLink(urlLike, noteIdLike) {
|
|
3284
|
+
const rawUrl = String(urlLike || "").trim();
|
|
3285
|
+
const noteId = normalizeNoteId(noteIdLike);
|
|
3286
|
+
if (rawUrl) {
|
|
3287
|
+
if (/^https?:\/\//i.test(rawUrl)) return { url: rawUrl, noteId };
|
|
3288
|
+
if (rawUrl.startsWith("/")) return { url: `https://www.xiaohongshu.com${rawUrl}`, noteId };
|
|
3289
|
+
if (/^[a-zA-Z0-9_-]{6,}$/.test(rawUrl)) {
|
|
3290
|
+
return { url: `https://www.xiaohongshu.com/explore/${rawUrl}`, noteId: noteId || rawUrl };
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (noteId) {
|
|
3294
|
+
return { url: `https://www.xiaohongshu.com/explore/${noteId}`, noteId };
|
|
3295
|
+
}
|
|
3296
|
+
return null;
|
|
3297
|
+
}
|
|
3298
|
+
function pushLikedLink(entry) {
|
|
3299
|
+
const url = String(entry.url || "").trim();
|
|
3300
|
+
if (!url) return;
|
|
3301
|
+
const previous = likedLinks.get(url);
|
|
3302
|
+
likedLinks.set(url, {
|
|
3303
|
+
url,
|
|
3304
|
+
noteId: entry.noteId || previous?.noteId || null,
|
|
3305
|
+
source: entry.source || previous?.source || "comment_like",
|
|
3306
|
+
profileId: entry.profileId || previous?.profileId || activeProfileId || null,
|
|
3307
|
+
ts: entry.ts || previous?.ts || (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
3308
|
+
count: (previous?.count || 0) + 1
|
|
3309
|
+
});
|
|
3310
|
+
const keys = Array.from(likedLinks.keys());
|
|
3311
|
+
if (keys.length > maxLikedLinks) {
|
|
3312
|
+
likedLinks.delete(keys[0]);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
function renderLikedLinks() {
|
|
3316
|
+
likedLinksList.innerHTML = "";
|
|
3317
|
+
const entries = Array.from(likedLinks.values());
|
|
3318
|
+
if (entries.length === 0) {
|
|
3319
|
+
likedLinksEmpty.style.display = "block";
|
|
3320
|
+
likedLinksList.style.display = "none";
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
likedLinksEmpty.style.display = "none";
|
|
3324
|
+
likedLinksList.style.display = "block";
|
|
3325
|
+
for (const item of entries) {
|
|
3326
|
+
const li = document.createElement("li");
|
|
3327
|
+
const wrap = document.createElement("div");
|
|
3328
|
+
wrap.style.display = "flex";
|
|
3329
|
+
wrap.style.alignItems = "center";
|
|
3330
|
+
wrap.style.gap = "6px";
|
|
3331
|
+
wrap.style.flexWrap = "wrap";
|
|
3332
|
+
const label = document.createElement("span");
|
|
3333
|
+
label.textContent = `[${item.ts}]`;
|
|
3334
|
+
wrap.appendChild(label);
|
|
3335
|
+
const link = document.createElement("a");
|
|
3336
|
+
link.href = "#";
|
|
3337
|
+
link.textContent = item.noteId ? `note:${item.noteId}` : "\u6253\u5F00\u94FE\u63A5";
|
|
3338
|
+
link.onclick = (evt) => {
|
|
3339
|
+
evt.preventDefault();
|
|
3340
|
+
void openLikedLink(item.url, item.profileId || activeProfileId || null);
|
|
3341
|
+
};
|
|
3342
|
+
wrap.appendChild(link);
|
|
3343
|
+
const hint = document.createElement("span");
|
|
3344
|
+
hint.style.color = "var(--text-4)";
|
|
3345
|
+
hint.textContent = `(${item.source}${item.count > 1 ? ` x${item.count}` : ""})`;
|
|
3346
|
+
wrap.appendChild(hint);
|
|
3347
|
+
li.appendChild(wrap);
|
|
3348
|
+
likedLinksList.appendChild(li);
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
async function openLikedLink(url, profileId) {
|
|
3352
|
+
const targetUrl = String(url || "").trim();
|
|
3353
|
+
if (!targetUrl) return;
|
|
3354
|
+
const pid = String(profileId || "").trim();
|
|
3355
|
+
try {
|
|
3356
|
+
if (pid && typeof ctx2.api?.cmdRunJson === "function") {
|
|
3357
|
+
const ret = await ctx2.api.cmdRunJson({
|
|
3358
|
+
title: `goto ${pid}`,
|
|
3359
|
+
cwd: "",
|
|
3360
|
+
args: [
|
|
3361
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
3362
|
+
"goto-profile",
|
|
3363
|
+
pid,
|
|
3364
|
+
"--url",
|
|
3365
|
+
targetUrl,
|
|
3366
|
+
"--json"
|
|
3367
|
+
],
|
|
3368
|
+
timeoutMs: 3e4
|
|
3369
|
+
});
|
|
3370
|
+
if (ret?.ok) {
|
|
3371
|
+
addLog(`\u5DF2\u5728 ${pid} \u6253\u5F00\u70B9\u8D5E\u94FE\u63A5`, "info");
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
if (typeof ctx2.api?.osOpenPath === "function") {
|
|
3376
|
+
await ctx2.api.osOpenPath(targetUrl);
|
|
3377
|
+
addLog("\u5DF2\u901A\u8FC7\u7CFB\u7EDF\u6253\u5F00\u70B9\u8D5E\u94FE\u63A5", "warn");
|
|
3378
|
+
}
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
pushRecentError("\u70B9\u8D5E\u94FE\u63A5\u6253\u5F00\u5931\u8D25", "like_link", err?.message || String(err));
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3121
3383
|
function renderRunSummary() {
|
|
3122
3384
|
runIdText.textContent = activeRunId || "-";
|
|
3385
|
+
if (ctx2 && typeof ctx2 === "object") {
|
|
3386
|
+
ctx2.activeRunId = activeRunId || null;
|
|
3387
|
+
}
|
|
3123
3388
|
errorCountText.textContent = String(errorCountTotal);
|
|
3124
3389
|
recentErrorsList.innerHTML = "";
|
|
3125
3390
|
if (recentErrors.length === 0) {
|
|
@@ -3131,18 +3396,35 @@ function renderDashboard(root, ctx2) {
|
|
|
3131
3396
|
recentErrorsList.style.display = "block";
|
|
3132
3397
|
recentErrors.forEach((item) => {
|
|
3133
3398
|
const li = document.createElement("li");
|
|
3134
|
-
|
|
3399
|
+
const line = document.createElement("div");
|
|
3400
|
+
line.textContent = `[${item.ts}] ${item.source}: ${item.message}`;
|
|
3401
|
+
li.appendChild(line);
|
|
3402
|
+
if (item.details) {
|
|
3403
|
+
const details = document.createElement("details");
|
|
3404
|
+
const summary = document.createElement("summary");
|
|
3405
|
+
summary.textContent = "\u8BE6\u60C5";
|
|
3406
|
+
const pre = document.createElement("pre");
|
|
3407
|
+
pre.style.margin = "4px 0 0 0";
|
|
3408
|
+
pre.style.whiteSpace = "pre-wrap";
|
|
3409
|
+
pre.style.wordBreak = "break-word";
|
|
3410
|
+
pre.textContent = item.details;
|
|
3411
|
+
details.appendChild(summary);
|
|
3412
|
+
details.appendChild(pre);
|
|
3413
|
+
li.appendChild(details);
|
|
3414
|
+
}
|
|
3135
3415
|
recentErrorsList.appendChild(li);
|
|
3136
3416
|
});
|
|
3417
|
+
renderLikedLinks();
|
|
3137
3418
|
}
|
|
3138
|
-
function pushRecentError(message, source = "runtime") {
|
|
3419
|
+
function pushRecentError(message, source = "runtime", details = null) {
|
|
3139
3420
|
const msg = String(message || "").trim();
|
|
3140
3421
|
if (!msg) return;
|
|
3141
3422
|
errorCountTotal += 1;
|
|
3142
3423
|
recentErrors.push({
|
|
3143
3424
|
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
3144
3425
|
source: String(source || "runtime").trim() || "runtime",
|
|
3145
|
-
message: msg
|
|
3426
|
+
message: msg,
|
|
3427
|
+
details: normalizeDetails(details)
|
|
3146
3428
|
});
|
|
3147
3429
|
while (recentErrors.length > maxRecentErrors) recentErrors.shift();
|
|
3148
3430
|
renderRunSummary();
|
|
@@ -3164,6 +3446,19 @@ function renderDashboard(root, ctx2) {
|
|
|
3164
3446
|
clearInterval(elapsedTimer);
|
|
3165
3447
|
elapsedTimer = null;
|
|
3166
3448
|
}
|
|
3449
|
+
function startStatePoll() {
|
|
3450
|
+
if (statePollTimer) return;
|
|
3451
|
+
if (typeof ctx2.api?.stateGetTasks !== "function") return;
|
|
3452
|
+
statePollTimer = setInterval(() => {
|
|
3453
|
+
if (paused) return;
|
|
3454
|
+
void fetchCurrentState();
|
|
3455
|
+
}, 5e3);
|
|
3456
|
+
}
|
|
3457
|
+
function stopStatePoll() {
|
|
3458
|
+
if (!statePollTimer) return;
|
|
3459
|
+
clearInterval(statePollTimer);
|
|
3460
|
+
statePollTimer = null;
|
|
3461
|
+
}
|
|
3167
3462
|
function addLog(line, type = "info") {
|
|
3168
3463
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
3169
3464
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -3176,8 +3471,39 @@ function renderDashboard(root, ctx2) {
|
|
|
3176
3471
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
3177
3472
|
}
|
|
3178
3473
|
}
|
|
3474
|
+
function resetDashboardForNewRun(reason, startedAtMs) {
|
|
3475
|
+
commentsCount = 0;
|
|
3476
|
+
likesCount = 0;
|
|
3477
|
+
likesSkippedCount = 0;
|
|
3478
|
+
likesAlreadyCount = 0;
|
|
3479
|
+
likesDedupCount = 0;
|
|
3480
|
+
errorCountTotal = 0;
|
|
3481
|
+
recentErrors.length = 0;
|
|
3482
|
+
likedLinks.clear();
|
|
3483
|
+
logsContainer.innerHTML = "";
|
|
3484
|
+
statCollected.textContent = "0";
|
|
3485
|
+
statSuccess.textContent = "0";
|
|
3486
|
+
statFailed.textContent = "0";
|
|
3487
|
+
statRemaining.textContent = "0";
|
|
3488
|
+
statComments.textContent = "0\u6761";
|
|
3489
|
+
statLikes.textContent = "0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)";
|
|
3490
|
+
progressPercent.textContent = "0%";
|
|
3491
|
+
progressBar.style.width = "0%";
|
|
3492
|
+
currentAction.textContent = reason || "-";
|
|
3493
|
+
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
3494
|
+
startTime = Number.isFinite(Number(startedAtMs)) && Number(startedAtMs) > 0 ? Number(startedAtMs) : Date.now();
|
|
3495
|
+
stoppedAt = null;
|
|
3496
|
+
updateElapsed();
|
|
3497
|
+
startElapsedTimer();
|
|
3498
|
+
renderRunSummary();
|
|
3499
|
+
}
|
|
3179
3500
|
function updateFromTaskState(state) {
|
|
3180
3501
|
if (!state) return;
|
|
3502
|
+
const incomingRunId = String(state.runId || "").trim();
|
|
3503
|
+
if (incomingRunId && activeRunId && incomingRunId !== activeRunId && (isTerminalStatus(activeStatus) || !isRunningStatus(activeStatus)) && isRunningStatus(state.status)) {
|
|
3504
|
+
activeRunId = incomingRunId;
|
|
3505
|
+
resetDashboardForNewRun("\u5207\u6362\u5230\u65B0\u4EFB\u52A1");
|
|
3506
|
+
}
|
|
3181
3507
|
const progressObj = state.progress && typeof state.progress === "object" ? state.progress : null;
|
|
3182
3508
|
const processedRaw = progressObj?.processed ?? progressObj?.current ?? state.progress ?? state.collected ?? state.current ?? 0;
|
|
3183
3509
|
const totalRaw = progressObj?.total ?? state.total ?? state.target ?? state.maxNotes ?? 0;
|
|
@@ -3230,6 +3556,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3230
3556
|
if (state.profileId) {
|
|
3231
3557
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3232
3558
|
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
3559
|
+
activeProfileId = String(state.profileId || "").trim();
|
|
3233
3560
|
}
|
|
3234
3561
|
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3235
3562
|
if (taskId) {
|
|
@@ -3271,26 +3598,32 @@ function renderDashboard(root, ctx2) {
|
|
|
3271
3598
|
}
|
|
3272
3599
|
}
|
|
3273
3600
|
if (state.error) {
|
|
3274
|
-
pushRecentError(String(state.error), "state");
|
|
3601
|
+
pushRecentError(String(state.error), "state", state);
|
|
3275
3602
|
}
|
|
3276
3603
|
}
|
|
3277
3604
|
function pickTaskFromList(tasks) {
|
|
3278
3605
|
const target = activeRunId;
|
|
3279
|
-
const running = tasks.find((item) => isRunningStatus(item?.status));
|
|
3280
3606
|
const sorted = [...tasks].sort((a, b) => {
|
|
3281
3607
|
const aTs = Number(a?.updatedAt ?? a?.completedAt ?? a?.startedAt ?? 0) || 0;
|
|
3282
3608
|
const bTs = Number(b?.updatedAt ?? b?.completedAt ?? b?.startedAt ?? 0) || 0;
|
|
3283
3609
|
return bTs - aTs;
|
|
3284
3610
|
});
|
|
3611
|
+
const running = sorted.find((item) => isRunningStatus(item?.status)) || null;
|
|
3285
3612
|
const latest = sorted[0] || null;
|
|
3613
|
+
const launchingFresh = Number.isFinite(contextStartedAtMs) && contextStartedAtMs > 0 && Date.now() - contextStartedAtMs < 12e4;
|
|
3286
3614
|
if (target) {
|
|
3287
3615
|
const matched = tasks.find((item) => String(item?.runId || "").trim() === target);
|
|
3288
3616
|
if (matched) {
|
|
3289
|
-
if (isRunningStatus(matched?.status)) return matched;
|
|
3290
|
-
if (running) return running;
|
|
3291
|
-
if (latest && String(latest?.runId || "").trim() !== target) return latest;
|
|
3292
3617
|
return matched;
|
|
3293
3618
|
}
|
|
3619
|
+
if (launchingFresh) {
|
|
3620
|
+
return null;
|
|
3621
|
+
}
|
|
3622
|
+
if (running) return running;
|
|
3623
|
+
return null;
|
|
3624
|
+
}
|
|
3625
|
+
if (launchingFresh) {
|
|
3626
|
+
return running || null;
|
|
3294
3627
|
}
|
|
3295
3628
|
return running || latest || null;
|
|
3296
3629
|
}
|
|
@@ -3301,29 +3634,14 @@ function renderDashboard(root, ctx2) {
|
|
|
3301
3634
|
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
3302
3635
|
currentAction.textContent = "\u542F\u52A8 autoscript";
|
|
3303
3636
|
activeStatus = "running";
|
|
3304
|
-
statCollected.textContent = "0";
|
|
3305
|
-
statSuccess.textContent = "0";
|
|
3306
|
-
statFailed.textContent = "0";
|
|
3307
|
-
statRemaining.textContent = "0";
|
|
3308
|
-
progressPercent.textContent = "0%";
|
|
3309
|
-
progressBar.style.width = "0%";
|
|
3310
|
-
commentsCount = 0;
|
|
3311
|
-
likesCount = 0;
|
|
3312
|
-
likesSkippedCount = 0;
|
|
3313
|
-
likesAlreadyCount = 0;
|
|
3314
|
-
likesDedupCount = 0;
|
|
3315
|
-
statComments.textContent = `0\u6761`;
|
|
3316
|
-
statLikes.textContent = `0\u6B21 (\u8DF3\u8FC7:0, \u5DF2\u8D5E:0, \u53BB\u91CD:0)`;
|
|
3317
3637
|
const ts = Date.parse(String(payload.ts || "")) || Date.now();
|
|
3318
|
-
startTime = ts;
|
|
3319
|
-
stoppedAt = null;
|
|
3320
|
-
updateElapsed();
|
|
3321
|
-
startElapsedTimer();
|
|
3322
3638
|
if (payload.runId) {
|
|
3323
3639
|
activeRunId = String(payload.runId || "").trim() || activeRunId;
|
|
3324
3640
|
}
|
|
3641
|
+
resetDashboardForNewRun("\u65B0\u4EFB\u52A1\u542F\u52A8", ts);
|
|
3325
3642
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
3326
3643
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3644
|
+
if (payload.profileId) activeProfileId = String(payload.profileId || "").trim();
|
|
3327
3645
|
if (payload.taskId) {
|
|
3328
3646
|
const taskId = String(payload.taskId || "").trim();
|
|
3329
3647
|
if (taskId) {
|
|
@@ -3372,6 +3690,23 @@ function renderDashboard(root, ctx2) {
|
|
|
3372
3690
|
likesAlreadyCount = Math.max(0, likesAlreadyCount + already);
|
|
3373
3691
|
likesDedupCount = Math.max(0, likesDedupCount + dedup);
|
|
3374
3692
|
statLikes.textContent = `${likesCount}\u6B21 (\u8DF3\u8FC7:${likesSkippedCount}, \u5DF2\u8D5E:${likesAlreadyCount}, \u53BB\u91CD:${likesDedupCount})`;
|
|
3693
|
+
const candidates = [];
|
|
3694
|
+
const direct = normalizeLink(opResult?.noteUrl || opResult?.url || opResult?.href || opResult?.link, opResult?.noteId);
|
|
3695
|
+
if (direct) candidates.push({ ...direct, source: "comment_like" });
|
|
3696
|
+
const likedComments = Array.isArray(opResult?.likedComments) ? opResult.likedComments : [];
|
|
3697
|
+
for (const row of likedComments) {
|
|
3698
|
+
const item = normalizeLink(row?.noteUrl || row?.url || row?.href || row?.link, row?.noteId || opResult?.noteId);
|
|
3699
|
+
if (!item) continue;
|
|
3700
|
+
candidates.push({ ...item, source: "liked_comment" });
|
|
3701
|
+
}
|
|
3702
|
+
for (const item of candidates) {
|
|
3703
|
+
pushLikedLink({
|
|
3704
|
+
...item,
|
|
3705
|
+
profileId: activeProfileId || null,
|
|
3706
|
+
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
renderRunSummary();
|
|
3375
3710
|
}
|
|
3376
3711
|
return;
|
|
3377
3712
|
}
|
|
@@ -3380,7 +3715,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3380
3715
|
statFailed.textContent = String(failed + 1);
|
|
3381
3716
|
const opId = String(payload?.operationId || "").trim();
|
|
3382
3717
|
const err = String(payload?.error || payload?.message || payload?.code || event).trim();
|
|
3383
|
-
pushRecentError(opId ? `${opId}: ${err}` : err, event);
|
|
3718
|
+
pushRecentError(opId ? `${opId}: ${err}` : err, event, payload);
|
|
3384
3719
|
return;
|
|
3385
3720
|
}
|
|
3386
3721
|
if (event === "xhs.unified.merged") {
|
|
@@ -3402,7 +3737,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3402
3737
|
currentPhase.textContent = reason && reason !== "script_failure" ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
3403
3738
|
currentAction.textContent = reason || "stop";
|
|
3404
3739
|
if (reason && !successReasons.has(reason)) {
|
|
3405
|
-
pushRecentError(`stop reason=${reason}`, event);
|
|
3740
|
+
pushRecentError(`stop reason=${reason}`, event, payload);
|
|
3406
3741
|
}
|
|
3407
3742
|
renderRunSummary();
|
|
3408
3743
|
}
|
|
@@ -3460,6 +3795,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3460
3795
|
if (profile?.profileId) {
|
|
3461
3796
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3462
3797
|
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3798
|
+
activeProfileId = String(profile.profileId || "").trim();
|
|
3463
3799
|
}
|
|
3464
3800
|
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3465
3801
|
if (runId) {
|
|
@@ -3535,7 +3871,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3535
3871
|
if (payload.action) addLog(String(payload.action), "info");
|
|
3536
3872
|
if (payload.error) {
|
|
3537
3873
|
addLog(String(payload.error), "error");
|
|
3538
|
-
pushRecentError(String(payload.error), "state");
|
|
3874
|
+
pushRecentError(String(payload.error), "state", payload);
|
|
3539
3875
|
}
|
|
3540
3876
|
}
|
|
3541
3877
|
});
|
|
@@ -3552,10 +3888,12 @@ function renderDashboard(root, ctx2) {
|
|
|
3552
3888
|
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
3553
3889
|
if (paused) return;
|
|
3554
3890
|
const runId = String(evt?.runId || "").trim();
|
|
3555
|
-
|
|
3891
|
+
const preferredRunId = String(ctx2?.activeRunId || "").trim();
|
|
3892
|
+
const shouldAdoptStartedRun = evt?.type === "started" && runId && isXhsCommandTitle(evt?.title) && (!activeRunId || isTerminalStatus(activeStatus) || preferredRunId && preferredRunId === runId);
|
|
3893
|
+
if (shouldAdoptStartedRun) {
|
|
3556
3894
|
activeRunId = runId;
|
|
3557
3895
|
activeStatus = "running";
|
|
3558
|
-
|
|
3896
|
+
resetDashboardForNewRun("\u8FDB\u7A0B\u542F\u52A8");
|
|
3559
3897
|
renderRunSummary();
|
|
3560
3898
|
}
|
|
3561
3899
|
if (activeRunId && runId && runId !== activeRunId) return;
|
|
@@ -3564,7 +3902,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3564
3902
|
parseLineEvent(String(evt.line || "").trim());
|
|
3565
3903
|
} else if (evt.type === "stderr") {
|
|
3566
3904
|
addLog(evt.line, "error");
|
|
3567
|
-
pushRecentError(String(evt.line || ""), "stderr");
|
|
3905
|
+
pushRecentError(String(evt.line || ""), "stderr", evt);
|
|
3568
3906
|
const failed = Number(statFailed.textContent || "0") || 0;
|
|
3569
3907
|
statFailed.textContent = String(failed + 1);
|
|
3570
3908
|
} else if (evt.type === "exit") {
|
|
@@ -3579,7 +3917,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3579
3917
|
stopElapsedTimer();
|
|
3580
3918
|
}
|
|
3581
3919
|
if (Number(evt.exitCode || 0) !== 0) {
|
|
3582
|
-
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit");
|
|
3920
|
+
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit", evt);
|
|
3583
3921
|
}
|
|
3584
3922
|
renderRunSummary();
|
|
3585
3923
|
}
|
|
@@ -3590,13 +3928,17 @@ function renderDashboard(root, ctx2) {
|
|
|
3590
3928
|
try {
|
|
3591
3929
|
const config = await ctx2.api.configLoadLast();
|
|
3592
3930
|
if (config) {
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3931
|
+
if (!hasRenderableValue(contextRun?.keyword)) {
|
|
3932
|
+
taskKeyword.textContent = config.keyword || "-";
|
|
3933
|
+
}
|
|
3934
|
+
if (!(Number(contextRun?.target) > 0)) {
|
|
3935
|
+
taskTarget.textContent = String(config.target || 50);
|
|
3936
|
+
}
|
|
3937
|
+
if (!hasRenderableValue(contextRun?.profileId) && config.lastProfileId) {
|
|
3596
3938
|
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
3597
3939
|
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
3598
3940
|
}
|
|
3599
|
-
const taskId = String(config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3941
|
+
const taskId = String(contextRun?.taskId || config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3600
3942
|
if (taskId) {
|
|
3601
3943
|
taskConfigId.textContent = taskId;
|
|
3602
3944
|
}
|
|
@@ -3660,23 +4002,25 @@ function renderDashboard(root, ctx2) {
|
|
|
3660
4002
|
}
|
|
3661
4003
|
setTimeout(() => {
|
|
3662
4004
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3663
|
-
ctx2.setActiveTab("
|
|
4005
|
+
ctx2.setActiveTab("tasks");
|
|
3664
4006
|
}
|
|
3665
4007
|
}, 1500);
|
|
3666
4008
|
}
|
|
3667
4009
|
};
|
|
3668
4010
|
backConfigBtn.onclick = () => {
|
|
3669
4011
|
if (typeof ctx2.setActiveTab === "function") {
|
|
3670
|
-
ctx2.setActiveTab("
|
|
4012
|
+
ctx2.setActiveTab("tasks");
|
|
3671
4013
|
}
|
|
3672
4014
|
};
|
|
3673
4015
|
renderRunSummary();
|
|
3674
4016
|
loadTaskInfo();
|
|
3675
4017
|
subscribeToUpdates();
|
|
3676
4018
|
fetchCurrentState();
|
|
4019
|
+
startStatePoll();
|
|
3677
4020
|
startElapsedTimer();
|
|
3678
4021
|
return () => {
|
|
3679
4022
|
stopElapsedTimer();
|
|
4023
|
+
stopStatePoll();
|
|
3680
4024
|
if (unsubscribeState) unsubscribeState();
|
|
3681
4025
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
3682
4026
|
if (unsubscribeBus) unsubscribeBus();
|
|
@@ -3724,6 +4068,14 @@ function toTimestamp(value) {
|
|
|
3724
4068
|
if (!Number.isFinite(parsed)) return null;
|
|
3725
4069
|
return parsed;
|
|
3726
4070
|
}
|
|
4071
|
+
function formatProfileTag2(profileId) {
|
|
4072
|
+
const id = String(profileId || "").trim();
|
|
4073
|
+
const m = id.match(/^profile-(\d+)$/i);
|
|
4074
|
+
if (!m) return id;
|
|
4075
|
+
const seq = Number(m[1]);
|
|
4076
|
+
if (!Number.isFinite(seq)) return id;
|
|
4077
|
+
return `P${String(seq).padStart(3, "0")}`;
|
|
4078
|
+
}
|
|
3727
4079
|
function renderAccountManager(root, ctx2) {
|
|
3728
4080
|
root.innerHTML = "";
|
|
3729
4081
|
const autoSyncTimers = /* @__PURE__ */ new Map();
|
|
@@ -3746,7 +4098,7 @@ function renderAccountManager(root, ctx2) {
|
|
|
3746
4098
|
</div>
|
|
3747
4099
|
<div class="env-item" id="env-firefox">
|
|
3748
4100
|
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
3749
|
-
<span
|
|
4101
|
+
<span>\u6D4F\u89C8\u5668\u5185\u6838\uFF08Camoufox Firefox\uFF09</span>
|
|
3750
4102
|
</div>
|
|
3751
4103
|
</div>
|
|
3752
4104
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
@@ -3789,15 +4141,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3789
4141
|
let busUnsubscribe = null;
|
|
3790
4142
|
async function checkEnvironment() {
|
|
3791
4143
|
try {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
updateEnvItem("env-
|
|
4144
|
+
if (typeof ctx2.api?.envCheckAll !== "function") {
|
|
4145
|
+
throw new Error("envCheckAll unavailable");
|
|
4146
|
+
}
|
|
4147
|
+
const unified = await ctx2.api.envCheckAll();
|
|
4148
|
+
if (!unified || typeof unified !== "object") {
|
|
4149
|
+
throw new Error("invalid envCheckAll response");
|
|
4150
|
+
}
|
|
4151
|
+
const browserReady = Boolean(unified.browserReady);
|
|
4152
|
+
updateEnvItem("env-camo", Boolean(unified.camo?.installed));
|
|
4153
|
+
updateEnvItem("env-unified", Boolean(unified.services?.unifiedApi));
|
|
4154
|
+
updateEnvItem("env-browser", Boolean(unified.services?.camoRuntime));
|
|
4155
|
+
updateEnvItem("env-firefox", browserReady);
|
|
3801
4156
|
} catch (err) {
|
|
3802
4157
|
console.error("Environment check failed:", err);
|
|
3803
4158
|
}
|
|
@@ -3811,6 +4166,18 @@ function renderAccountManager(root, ctx2) {
|
|
|
3811
4166
|
envCheckInFlight = false;
|
|
3812
4167
|
}
|
|
3813
4168
|
}
|
|
4169
|
+
async function cleanupEnvironment() {
|
|
4170
|
+
try {
|
|
4171
|
+
console.log("[account-manager] Starting environment cleanup...");
|
|
4172
|
+
const result = await ctx2.api.envCleanup();
|
|
4173
|
+
console.log("[account-manager] Cleanup result:", result);
|
|
4174
|
+
alert("\u73AF\u5883\u6E05\u7406\u5B8C\u6210\uFF01\\n" + JSON.stringify(result, null, 2));
|
|
4175
|
+
await checkEnvironment();
|
|
4176
|
+
} catch (err) {
|
|
4177
|
+
console.error("Environment cleanup failed:", err);
|
|
4178
|
+
alert("\u73AF\u5883\u6E05\u7406\u5931\u8D25\uFF1A" + err.message);
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
3814
4181
|
function updateEnvItem(id, ok) {
|
|
3815
4182
|
const el = root.querySelector(`#${id}`);
|
|
3816
4183
|
if (!el) return;
|
|
@@ -3826,7 +4193,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3826
4193
|
platform: normalizePlatform(row.platform),
|
|
3827
4194
|
statusView: row.valid ? "valid" : row.status === "pending" ? "pending" : "expired",
|
|
3828
4195
|
lastCheckAt: toTimestamp(row.updatedAt)
|
|
3829
|
-
}))
|
|
4196
|
+
})).sort((a, b) => {
|
|
4197
|
+
const p = String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
4198
|
+
if (p !== 0) return p;
|
|
4199
|
+
return String(a.platform || "").localeCompare(String(b.platform || ""));
|
|
4200
|
+
});
|
|
3830
4201
|
renderAccountList();
|
|
3831
4202
|
} catch (err) {
|
|
3832
4203
|
console.error("Failed to load accounts:", err);
|
|
@@ -3860,11 +4231,11 @@ function renderAccountManager(root, ctx2) {
|
|
|
3860
4231
|
const nameDiv = createEl("div", { style: "min-width: 0; flex: 1;" }, [
|
|
3861
4232
|
createEl("div", { className: "account-name", style: "display: flex; gap: 6px; align-items: center;" }, [
|
|
3862
4233
|
createEl("span", { style: "font-size: 13px;" }, [platform.icon]),
|
|
3863
|
-
createEl("span", {}, [acc.alias || acc.name || acc.profileId]),
|
|
4234
|
+
createEl("span", {}, [acc.alias || acc.name || formatProfileTag2(acc.profileId)]),
|
|
3864
4235
|
createEl("span", { style: "font-size: 11px; color: var(--text-3);" }, [platform.label])
|
|
3865
4236
|
]),
|
|
3866
4237
|
createEl("div", { className: "account-alias", style: "font-size: 11px; color: var(--text-3);" }, [
|
|
3867
|
-
`profile: ${acc.profileId} \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
4238
|
+
`profile: ${formatProfileTag2(acc.profileId)} (${acc.profileId}) \xB7 \u4E0A\u6B21\u68C0\u67E5: ${formatTs(acc.lastCheckAt)}`
|
|
3868
4239
|
])
|
|
3869
4240
|
]);
|
|
3870
4241
|
const statusBadge = createEl("span", {
|
|
@@ -3992,6 +4363,7 @@ Profile ID: ${acc.profileId}
|
|
|
3992
4363
|
async function openAccountLogin(account, options = {}) {
|
|
3993
4364
|
if (!String(account.profileId || "").trim()) return false;
|
|
3994
4365
|
const platform = getPlatformInfo(account.platform);
|
|
4366
|
+
const idleTimeout = String(ctx2.api?.settings?.idleTimeout || "30m").trim() || "30m";
|
|
3995
4367
|
const timeoutSec = Math.max(30, Number(ctx2.api?.settings?.timeouts?.loginTimeoutSec || 900));
|
|
3996
4368
|
account.status = "pending";
|
|
3997
4369
|
account.statusView = "pending";
|
|
@@ -4007,6 +4379,8 @@ Profile ID: ${acc.profileId}
|
|
|
4007
4379
|
account.profileId,
|
|
4008
4380
|
"--url",
|
|
4009
4381
|
platform.loginUrl,
|
|
4382
|
+
"--idle-timeout",
|
|
4383
|
+
idleTimeout,
|
|
4010
4384
|
"--wait-sync",
|
|
4011
4385
|
"false",
|
|
4012
4386
|
"--timeout-sec",
|
|
@@ -4052,6 +4426,7 @@ Profile ID: ${acc.profileId}
|
|
|
4052
4426
|
await ctx2.refreshSettings();
|
|
4053
4427
|
}
|
|
4054
4428
|
}
|
|
4429
|
+
const idleTimeout = String(ctx2.api?.settings?.idleTimeout || "30m").trim() || "30m";
|
|
4055
4430
|
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
4056
4431
|
await ctx2.api.cmdSpawn({
|
|
4057
4432
|
title: `\u767B\u5F55 ${alias || profileId}`,
|
|
@@ -4060,6 +4435,8 @@ Profile ID: ${acc.profileId}
|
|
|
4060
4435
|
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
4061
4436
|
"login-profile",
|
|
4062
4437
|
profileId,
|
|
4438
|
+
"--idle-timeout",
|
|
4439
|
+
idleTimeout,
|
|
4063
4440
|
"--wait-sync",
|
|
4064
4441
|
"false",
|
|
4065
4442
|
"--timeout-sec",
|
|
@@ -4186,11 +4563,6 @@ Profile ID: ${acc.profileId}
|
|
|
4186
4563
|
}
|
|
4187
4564
|
|
|
4188
4565
|
// src/renderer/tabs-new/scheduler.mts
|
|
4189
|
-
function commandTypeToWeiboTaskType2(commandType) {
|
|
4190
|
-
if (commandType === "weibo-search") return "search";
|
|
4191
|
-
if (commandType === "weibo-monitor") return "monitor";
|
|
4192
|
-
return "timeline";
|
|
4193
|
-
}
|
|
4194
4566
|
function renderSchedulerPanel(root, ctx2) {
|
|
4195
4567
|
root.innerHTML = "";
|
|
4196
4568
|
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
@@ -4259,18 +4631,25 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4259
4631
|
<div>
|
|
4260
4632
|
<label>\u8C03\u5EA6\u7C7B\u578B</label>
|
|
4261
4633
|
<select id="scheduler-type" style="width: 140px;">
|
|
4262
|
-
<option value="
|
|
4263
|
-
<option value="
|
|
4634
|
+
<option value="immediate">\u9A6C\u4E0A\u6267\u884C\uFF08\u4EC5\u4E00\u6B21\uFF09</option>
|
|
4635
|
+
<option value="periodic">\u5468\u671F\u4EFB\u52A1</option>
|
|
4636
|
+
<option value="scheduled">\u5B9A\u65F6\u4EFB\u52A1</option>
|
|
4637
|
+
</select>
|
|
4638
|
+
</div>
|
|
4639
|
+
<div id="scheduler-periodic-type-wrap" style="display:none;">
|
|
4640
|
+
<label>\u5468\u671F\u7C7B\u578B</label>
|
|
4641
|
+
<select id="scheduler-periodic-type" style="width: 120px;">
|
|
4642
|
+
<option value="interval">\u6309\u95F4\u9694</option>
|
|
4264
4643
|
<option value="daily">\u6BCF\u5929</option>
|
|
4265
4644
|
<option value="weekly">\u6BCF\u5468</option>
|
|
4266
4645
|
</select>
|
|
4267
4646
|
</div>
|
|
4268
|
-
<div id="scheduler-interval-wrap">
|
|
4647
|
+
<div id="scheduler-interval-wrap" style="display:none;">
|
|
4269
4648
|
<label>\u95F4\u9694\u5206\u949F</label>
|
|
4270
4649
|
<input id="scheduler-interval" type="number" min="1" value="30" style="width: 120px;" />
|
|
4271
4650
|
</div>
|
|
4272
4651
|
<div id="scheduler-runat-wrap" style="display:none;">
|
|
4273
|
-
<label>\
|
|
4652
|
+
<label>\u6267\u884C\u65F6\u95F4</label>
|
|
4274
4653
|
<input id="scheduler-runat" type="datetime-local" style="width: 220px;" />
|
|
4275
4654
|
</div>
|
|
4276
4655
|
<div>
|
|
@@ -4280,8 +4659,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4280
4659
|
</div>
|
|
4281
4660
|
<div class="row">
|
|
4282
4661
|
<div>
|
|
4283
|
-
<label>Profile</label>
|
|
4284
|
-
<input id="scheduler-profile" placeholder="
|
|
4662
|
+
<label>Profile\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u9009\uFF09</label>
|
|
4663
|
+
<input id="scheduler-profile" placeholder="\u7559\u7A7A\u81EA\u52A8\u9009\u62E9\u8BE5\u5E73\u53F0\u6709\u6548\u8D26\u53F7" style="width: 260px;" />
|
|
4664
|
+
<div id="scheduler-profile-hint" class="muted" style="font-size:11px; margin-top:2px;">\u63A8\u8350: -</div>
|
|
4285
4665
|
</div>
|
|
4286
4666
|
<div>
|
|
4287
4667
|
<label>\u5173\u952E\u8BCD</label>
|
|
@@ -4331,6 +4711,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4331
4711
|
</div>
|
|
4332
4712
|
<div class="btn-group" style="margin-top: var(--gap);">
|
|
4333
4713
|
<button id="scheduler-save-btn" style="flex:1;">\u4FDD\u5B58\u4EFB\u52A1</button>
|
|
4714
|
+
<button id="scheduler-run-now-btn" class="secondary" style="flex:1;">\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)</button>
|
|
4334
4715
|
<button id="scheduler-reset-btn" class="secondary" style="flex:1;">\u6E05\u7A7A\u8868\u5355</button>
|
|
4335
4716
|
</div>
|
|
4336
4717
|
`;
|
|
@@ -4359,12 +4740,15 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4359
4740
|
const nameInput = root.querySelector("#scheduler-name");
|
|
4360
4741
|
const enabledInput = root.querySelector("#scheduler-enabled");
|
|
4361
4742
|
const typeSelect = root.querySelector("#scheduler-type");
|
|
4743
|
+
const periodicTypeWrap = root.querySelector("#scheduler-periodic-type-wrap");
|
|
4744
|
+
const periodicTypeSelect = root.querySelector("#scheduler-periodic-type");
|
|
4362
4745
|
const intervalWrap = root.querySelector("#scheduler-interval-wrap");
|
|
4363
4746
|
const runAtWrap = root.querySelector("#scheduler-runat-wrap");
|
|
4364
4747
|
const intervalInput = root.querySelector("#scheduler-interval");
|
|
4365
4748
|
const runAtInput = root.querySelector("#scheduler-runat");
|
|
4366
4749
|
const maxRunsInput = root.querySelector("#scheduler-max-runs");
|
|
4367
4750
|
const profileInput = root.querySelector("#scheduler-profile");
|
|
4751
|
+
const profileHint = root.querySelector("#scheduler-profile-hint");
|
|
4368
4752
|
const keywordInput = root.querySelector("#scheduler-keyword");
|
|
4369
4753
|
const userIdWrap = root.querySelector("#scheduler-user-id-wrap");
|
|
4370
4754
|
const userIdInput = root.querySelector("#scheduler-user-id");
|
|
@@ -4376,8 +4760,10 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4376
4760
|
const dryRunInput = root.querySelector("#scheduler-dryrun");
|
|
4377
4761
|
const likeKeywordsInput = root.querySelector("#scheduler-like-keywords");
|
|
4378
4762
|
const saveBtn = root.querySelector("#scheduler-save-btn");
|
|
4763
|
+
const runNowBtn = root.querySelector("#scheduler-run-now-btn");
|
|
4379
4764
|
const resetBtn = root.querySelector("#scheduler-reset-btn");
|
|
4380
4765
|
let tasks = [];
|
|
4766
|
+
let accountRows = [];
|
|
4381
4767
|
let daemonRunId = "";
|
|
4382
4768
|
let unsubscribeCmd = null;
|
|
4383
4769
|
let pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4394,14 +4780,21 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4394
4780
|
function openConfigTab(taskId) {
|
|
4395
4781
|
setActiveTaskContext(taskId);
|
|
4396
4782
|
if (typeof ctx2.setActiveTab === "function") {
|
|
4397
|
-
ctx2.setActiveTab("
|
|
4783
|
+
ctx2.setActiveTab("tasks");
|
|
4398
4784
|
}
|
|
4399
4785
|
}
|
|
4400
4786
|
function updateTypeFields() {
|
|
4401
|
-
const mode = typeSelect.value;
|
|
4402
|
-
const
|
|
4403
|
-
|
|
4404
|
-
|
|
4787
|
+
const mode = String(typeSelect.value || "immediate").trim();
|
|
4788
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
4789
|
+
const periodic = mode === "periodic";
|
|
4790
|
+
const scheduled = mode === "scheduled";
|
|
4791
|
+
periodicTypeWrap.style.display = periodic ? "" : "none";
|
|
4792
|
+
runAtWrap.style.display = scheduled || periodic && periodicType !== "interval" ? "" : "none";
|
|
4793
|
+
intervalWrap.style.display = periodic && periodicType === "interval" ? "" : "none";
|
|
4794
|
+
maxRunsInput.disabled = mode === "immediate" || mode === "scheduled";
|
|
4795
|
+
if (mode === "immediate" || mode === "scheduled") {
|
|
4796
|
+
maxRunsInput.value = "";
|
|
4797
|
+
}
|
|
4405
4798
|
}
|
|
4406
4799
|
function updateTaskTypeOptions() {
|
|
4407
4800
|
const platform = platformSelect.value;
|
|
@@ -4411,6 +4804,36 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4411
4804
|
taskTypeSelect.value = taskTypeSelect.options[0]?.value || "";
|
|
4412
4805
|
}
|
|
4413
4806
|
updatePlatformFields();
|
|
4807
|
+
void refreshPlatformAccounts(platform);
|
|
4808
|
+
}
|
|
4809
|
+
function normalizePlatform2(value) {
|
|
4810
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
4811
|
+
if (raw === "weibo") return "weibo";
|
|
4812
|
+
if (raw === "1688") return "1688";
|
|
4813
|
+
return "xiaohongshu";
|
|
4814
|
+
}
|
|
4815
|
+
async function refreshPlatformAccounts(platformValue) {
|
|
4816
|
+
const platform = normalizePlatform2(platformValue);
|
|
4817
|
+
try {
|
|
4818
|
+
accountRows = await listAccountProfiles(ctx2.api, { platform: platform === "xiaohongshu" ? "xiaohongshu" : platform });
|
|
4819
|
+
} catch {
|
|
4820
|
+
accountRows = [];
|
|
4821
|
+
}
|
|
4822
|
+
const recommended = accountRows.filter((row) => row.valid).sort((a, b) => {
|
|
4823
|
+
const ta = Date.parse(String(a.updatedAt || "")) || 0;
|
|
4824
|
+
const tb = Date.parse(String(b.updatedAt || "")) || 0;
|
|
4825
|
+
if (tb !== ta) return tb - ta;
|
|
4826
|
+
return String(a.profileId || "").localeCompare(String(b.profileId || ""));
|
|
4827
|
+
})[0];
|
|
4828
|
+
if (!recommended) {
|
|
4829
|
+
profileHint.textContent = `\u63A8\u8350: \u5F53\u524D\u5E73\u53F0(${platform})\u65E0\u6709\u6548\u8D26\u53F7`;
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
const label = recommended.alias || recommended.name || recommended.profileId;
|
|
4833
|
+
profileHint.textContent = `\u63A8\u8350: ${label} (${recommended.profileId})`;
|
|
4834
|
+
if (!String(profileInput.value || "").trim()) {
|
|
4835
|
+
profileInput.value = recommended.profileId;
|
|
4836
|
+
}
|
|
4414
4837
|
}
|
|
4415
4838
|
function updatePlatformFields() {
|
|
4416
4839
|
const commandType = String(taskTypeSelect.value || "").trim();
|
|
@@ -4423,7 +4846,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4423
4846
|
editingIdInput.value = "";
|
|
4424
4847
|
nameInput.value = "";
|
|
4425
4848
|
enabledInput.checked = true;
|
|
4426
|
-
typeSelect.value = "
|
|
4849
|
+
typeSelect.value = "immediate";
|
|
4850
|
+
periodicTypeSelect.value = "interval";
|
|
4427
4851
|
intervalInput.value = "30";
|
|
4428
4852
|
runAtInput.value = "";
|
|
4429
4853
|
maxRunsInput.value = "";
|
|
@@ -4446,7 +4870,6 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4446
4870
|
const maxRuns = maxRunsRaw ? Math.max(1, Number(maxRunsRaw) || 1) : null;
|
|
4447
4871
|
const commandType = String(taskTypeSelect.value || "xhs-unified").trim();
|
|
4448
4872
|
const argv = {
|
|
4449
|
-
profile: profileInput.value.trim(),
|
|
4450
4873
|
keyword: keywordInput.value.trim(),
|
|
4451
4874
|
"max-notes": Number(maxNotesInput.value || 50) || 50,
|
|
4452
4875
|
env: envSelect.value,
|
|
@@ -4456,19 +4879,40 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4456
4879
|
headless: headlessInput.checked,
|
|
4457
4880
|
"dry-run": dryRunInput.checked
|
|
4458
4881
|
};
|
|
4882
|
+
const profileValue = profileInput.value.trim();
|
|
4883
|
+
if (profileValue) argv.profile = profileValue;
|
|
4459
4884
|
if (commandType.startsWith("weibo")) {
|
|
4460
|
-
argv["task-type"] = commandTypeToWeiboTaskType2(commandType);
|
|
4461
4885
|
argv["user-id"] = userIdInput.value.trim();
|
|
4462
4886
|
}
|
|
4887
|
+
const mode = String(typeSelect.value || "immediate").trim();
|
|
4888
|
+
const periodicType = String(periodicTypeSelect.value || "interval").trim();
|
|
4889
|
+
let scheduleType = "once";
|
|
4890
|
+
let runAt = toIsoOrNull(runAtInput.value);
|
|
4891
|
+
let maxRunsFinal = maxRuns;
|
|
4892
|
+
if (mode === "immediate") {
|
|
4893
|
+
scheduleType = "once";
|
|
4894
|
+
runAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4895
|
+
maxRunsFinal = 1;
|
|
4896
|
+
} else if (mode === "periodic") {
|
|
4897
|
+
if (periodicType === "daily" || periodicType === "weekly") {
|
|
4898
|
+
scheduleType = periodicType;
|
|
4899
|
+
} else {
|
|
4900
|
+
scheduleType = "interval";
|
|
4901
|
+
runAt = null;
|
|
4902
|
+
}
|
|
4903
|
+
} else {
|
|
4904
|
+
scheduleType = "once";
|
|
4905
|
+
maxRunsFinal = 1;
|
|
4906
|
+
}
|
|
4463
4907
|
return {
|
|
4464
4908
|
id: editingIdInput.value.trim(),
|
|
4465
4909
|
name: nameInput.value.trim(),
|
|
4466
4910
|
enabled: enabledInput.checked,
|
|
4467
4911
|
commandType,
|
|
4468
|
-
scheduleType
|
|
4912
|
+
scheduleType,
|
|
4469
4913
|
intervalMinutes: Number(intervalInput.value || 30) || 30,
|
|
4470
|
-
runAt
|
|
4471
|
-
maxRuns,
|
|
4914
|
+
runAt,
|
|
4915
|
+
maxRuns: maxRunsFinal,
|
|
4472
4916
|
argv
|
|
4473
4917
|
};
|
|
4474
4918
|
}
|
|
@@ -4481,7 +4925,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4481
4925
|
editingIdInput.value = task.id;
|
|
4482
4926
|
nameInput.value = task.name || "";
|
|
4483
4927
|
enabledInput.checked = task.enabled !== false;
|
|
4484
|
-
|
|
4928
|
+
const uiSchedule = inferUiScheduleEditorState(task);
|
|
4929
|
+
typeSelect.value = uiSchedule.mode;
|
|
4930
|
+
periodicTypeSelect.value = uiSchedule.periodicType;
|
|
4485
4931
|
intervalInput.value = String(task.intervalMinutes || 30);
|
|
4486
4932
|
runAtInput.value = toLocalDatetimeValue(task.runAt);
|
|
4487
4933
|
maxRunsInput.value = task.maxRuns ? String(task.maxRuns) : "";
|
|
@@ -4499,19 +4945,27 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4499
4945
|
updatePlatformFields();
|
|
4500
4946
|
updateTypeFields();
|
|
4501
4947
|
}
|
|
4502
|
-
async function
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
args: [script, ...args, "--json"],
|
|
4508
|
-
timeoutMs
|
|
4509
|
-
});
|
|
4948
|
+
async function invokeSchedule(input) {
|
|
4949
|
+
if (typeof ctx2.api?.scheduleInvoke !== "function") {
|
|
4950
|
+
throw new Error("scheduleInvoke unavailable");
|
|
4951
|
+
}
|
|
4952
|
+
const ret = await ctx2.api.scheduleInvoke(input);
|
|
4510
4953
|
if (!ret?.ok) {
|
|
4511
|
-
const reason = String(ret?.error ||
|
|
4954
|
+
const reason = String(ret?.error || "schedule command failed").trim();
|
|
4512
4955
|
throw new Error(reason || "schedule command failed");
|
|
4513
4956
|
}
|
|
4514
|
-
return ret
|
|
4957
|
+
return ret?.json ?? ret;
|
|
4958
|
+
}
|
|
4959
|
+
async function invokeTaskRunEphemeral(input) {
|
|
4960
|
+
if (typeof ctx2.api?.taskRunEphemeral !== "function") {
|
|
4961
|
+
throw new Error("taskRunEphemeral unavailable");
|
|
4962
|
+
}
|
|
4963
|
+
const ret = await ctx2.api.taskRunEphemeral(input);
|
|
4964
|
+
if (!ret?.ok) {
|
|
4965
|
+
const reason = String(ret?.error || "run ephemeral failed").trim();
|
|
4966
|
+
throw new Error(reason || "run ephemeral failed");
|
|
4967
|
+
}
|
|
4968
|
+
return ret;
|
|
4515
4969
|
}
|
|
4516
4970
|
function downloadJson(fileName, payload) {
|
|
4517
4971
|
const text = JSON.stringify(payload, null, 2);
|
|
@@ -4533,7 +4987,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4533
4987
|
const card = createEl("div", {
|
|
4534
4988
|
style: "border:1px solid var(--border); border-radius:10px; padding:10px; margin-bottom:10px; background: var(--panel-soft);"
|
|
4535
4989
|
});
|
|
4536
|
-
const scheduleText = task.scheduleType === "once" ?
|
|
4990
|
+
const scheduleText = task.scheduleType === "once" ? `\u5B9A\u65F6\u4EFB\u52A1 @ ${task.runAt || "-"}` : task.scheduleType === "daily" ? `\u5468\u671F\u4EFB\u52A1(\u65E5) @ ${task.runAt || "-"}` : task.scheduleType === "weekly" ? `\u5468\u671F\u4EFB\u52A1(\u5468) @ ${task.runAt || "-"}` : `\u5468\u671F\u4EFB\u52A1(\u95F4\u9694 ${task.intervalMinutes}m)`;
|
|
4537
4991
|
const statusText = task.lastStatus ? `${task.lastStatus} / run=${task.runCount} / fail=${task.failCount}` : "never run";
|
|
4538
4992
|
const headRow = createEl("div", { style: "display:flex; justify-content:space-between; gap:8px; margin-bottom:6px;" });
|
|
4539
4993
|
headRow.appendChild(createEl("div", { style: "font-weight:600;" }, [task.name || task.id]));
|
|
@@ -4582,7 +5036,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4582
5036
|
runBtn.onclick = async () => {
|
|
4583
5037
|
try {
|
|
4584
5038
|
setActiveTaskContext(task.id);
|
|
4585
|
-
const out = await
|
|
5039
|
+
const out = await invokeSchedule({ action: "run", taskId: task.id, timeoutMs: 0 });
|
|
4586
5040
|
const runId = String(
|
|
4587
5041
|
out?.result?.runResult?.lastRunId || out?.result?.runResult?.runId || out?.runResult?.runId || ""
|
|
4588
5042
|
).trim();
|
|
@@ -4596,6 +5050,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4596
5050
|
target: Number(argv["max-notes"] || argv.target || 0) || 0,
|
|
4597
5051
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4598
5052
|
};
|
|
5053
|
+
ctx2.activeRunId = runId || null;
|
|
4599
5054
|
}
|
|
4600
5055
|
if (typeof ctx2.setStatus === "function") {
|
|
4601
5056
|
ctx2.setStatus(`running: ${task.id}`);
|
|
@@ -4610,7 +5065,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4610
5065
|
};
|
|
4611
5066
|
exportBtn.onclick = async () => {
|
|
4612
5067
|
try {
|
|
4613
|
-
const out = await
|
|
5068
|
+
const out = await invokeSchedule({ action: "export", taskId: task.id });
|
|
4614
5069
|
downloadJson(`${task.id}.json`, out);
|
|
4615
5070
|
} catch (err) {
|
|
4616
5071
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4619,7 +5074,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4619
5074
|
delBtn.onclick = async () => {
|
|
4620
5075
|
if (!confirm(`\u786E\u8BA4\u5220\u9664\u4EFB\u52A1 ${task.id} ?`)) return;
|
|
4621
5076
|
try {
|
|
4622
|
-
await
|
|
5077
|
+
await invokeSchedule({ action: "delete", taskId: task.id });
|
|
4623
5078
|
await refreshList();
|
|
4624
5079
|
} catch (err) {
|
|
4625
5080
|
alert(`\u5220\u9664\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4629,7 +5084,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4629
5084
|
}
|
|
4630
5085
|
}
|
|
4631
5086
|
async function refreshList() {
|
|
4632
|
-
const out = await
|
|
5087
|
+
const out = await invokeSchedule({ action: "list" });
|
|
4633
5088
|
tasks = parseTaskRows(out);
|
|
4634
5089
|
if (!pendingFocusTaskId) {
|
|
4635
5090
|
pendingFocusTaskId = String(ctx2?.activeTaskConfigId || "").trim();
|
|
@@ -4649,48 +5104,8 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4649
5104
|
}
|
|
4650
5105
|
async function saveTask() {
|
|
4651
5106
|
const payload = readFormAsPayload();
|
|
4652
|
-
if (!payload.name) {
|
|
4653
|
-
alert("\u4EFB\u52A1\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4654
|
-
return;
|
|
4655
|
-
}
|
|
4656
|
-
if (!payload.argv.profile && !payload.argv.profiles && !payload.argv.profilepool) {
|
|
4657
|
-
alert("profile/profiles/profilepool \u81F3\u5C11\u586B\u5199\u4E00\u4E2A");
|
|
4658
|
-
return;
|
|
4659
|
-
}
|
|
4660
|
-
const commandType = String(payload.commandType || "").trim();
|
|
4661
|
-
const keywordRequired = commandType === "xhs-unified" || commandType === "weibo-search" || commandType === "1688-search";
|
|
4662
|
-
if (keywordRequired && !payload.argv.keyword) {
|
|
4663
|
-
alert("\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4664
|
-
return;
|
|
4665
|
-
}
|
|
4666
|
-
if (commandType === "weibo-monitor" && !payload.argv["user-id"]) {
|
|
4667
|
-
alert("\u5FAE\u535A monitor \u4EFB\u52A1\u9700\u8981 user-id");
|
|
4668
|
-
return;
|
|
4669
|
-
}
|
|
4670
|
-
const args = payload.id ? ["update", payload.id] : ["add"];
|
|
4671
|
-
args.push("--name", payload.name);
|
|
4672
|
-
args.push("--enabled", String(payload.enabled));
|
|
4673
|
-
args.push("--command-type", commandType || "xhs-unified");
|
|
4674
|
-
args.push("--schedule-type", payload.scheduleType);
|
|
4675
|
-
if (payload.scheduleType === "once") {
|
|
4676
|
-
if (!payload.runAt) {
|
|
4677
|
-
alert("\u4E00\u6B21\u6027\u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4");
|
|
4678
|
-
return;
|
|
4679
|
-
}
|
|
4680
|
-
args.push("--run-at", payload.runAt);
|
|
4681
|
-
} else if (payload.scheduleType === "daily" || payload.scheduleType === "weekly") {
|
|
4682
|
-
if (!payload.runAt) {
|
|
4683
|
-
alert(`${payload.scheduleType} \u4EFB\u52A1\u9700\u8981\u951A\u70B9\u65F6\u95F4`);
|
|
4684
|
-
return;
|
|
4685
|
-
}
|
|
4686
|
-
args.push("--run-at", payload.runAt);
|
|
4687
|
-
} else {
|
|
4688
|
-
args.push("--interval-minutes", String(Math.max(1, payload.intervalMinutes)));
|
|
4689
|
-
}
|
|
4690
|
-
args.push("--max-runs", payload.maxRuns === null ? "0" : String(payload.maxRuns));
|
|
4691
|
-
args.push("--argv-json", JSON.stringify(payload.argv));
|
|
4692
5107
|
try {
|
|
4693
|
-
const out = await
|
|
5108
|
+
const out = await invokeSchedule({ action: "save", payload });
|
|
4694
5109
|
const savedId = String(out?.task?.id || payload.id || "").trim();
|
|
4695
5110
|
pendingFocusTaskId = savedId;
|
|
4696
5111
|
if (savedId) setActiveTaskContext(savedId);
|
|
@@ -4699,9 +5114,44 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4699
5114
|
alert(`\u4FDD\u5B58\u5931\u8D25: ${err?.message || String(err)}`);
|
|
4700
5115
|
}
|
|
4701
5116
|
}
|
|
5117
|
+
async function runNowFromForm() {
|
|
5118
|
+
runNowBtn.disabled = true;
|
|
5119
|
+
const prevText = runNowBtn.textContent;
|
|
5120
|
+
runNowBtn.textContent = "\u6267\u884C\u4E2D...";
|
|
5121
|
+
try {
|
|
5122
|
+
const payload = readFormAsPayload();
|
|
5123
|
+
const ret = await invokeTaskRunEphemeral({
|
|
5124
|
+
commandType: payload.commandType,
|
|
5125
|
+
argv: payload.argv
|
|
5126
|
+
});
|
|
5127
|
+
const runId = String(ret?.runId || "").trim();
|
|
5128
|
+
if (payload.commandType === "xhs-unified" && ctx2 && typeof ctx2 === "object") {
|
|
5129
|
+
ctx2.xhsCurrentRun = {
|
|
5130
|
+
runId: runId || null,
|
|
5131
|
+
taskId: null,
|
|
5132
|
+
profileId: String(payload.argv.profile || ""),
|
|
5133
|
+
keyword: String(payload.argv.keyword || ""),
|
|
5134
|
+
target: Number(payload.argv["max-notes"] || payload.argv.target || 0) || 0,
|
|
5135
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5136
|
+
};
|
|
5137
|
+
ctx2.activeRunId = runId || null;
|
|
5138
|
+
}
|
|
5139
|
+
if (typeof ctx2.setStatus === "function") {
|
|
5140
|
+
ctx2.setStatus(`started: ${payload.commandType}`);
|
|
5141
|
+
}
|
|
5142
|
+
if (payload.commandType === "xhs-unified" && typeof ctx2.setActiveTab === "function") {
|
|
5143
|
+
ctx2.setActiveTab("dashboard");
|
|
5144
|
+
}
|
|
5145
|
+
} catch (err) {
|
|
5146
|
+
alert(`\u6267\u884C\u5931\u8D25: ${err?.message || String(err)}`);
|
|
5147
|
+
} finally {
|
|
5148
|
+
runNowBtn.disabled = false;
|
|
5149
|
+
runNowBtn.textContent = prevText || "\u7ACB\u5373\u6267\u884C(\u4E0D\u4FDD\u5B58)";
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
4702
5152
|
async function runDueNow() {
|
|
4703
5153
|
try {
|
|
4704
|
-
const out = await
|
|
5154
|
+
const out = await invokeSchedule({ action: "run-due", limit: 20, timeoutMs: 0 });
|
|
4705
5155
|
alert(`\u5230\u70B9\u4EFB\u52A1\u6267\u884C\u5B8C\u6210\uFF1Adue=${out.count || 0}, success=${out.success || 0}, failed=${out.failed || 0}`);
|
|
4706
5156
|
await refreshList();
|
|
4707
5157
|
} catch (err) {
|
|
@@ -4710,7 +5160,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4710
5160
|
}
|
|
4711
5161
|
async function exportAll() {
|
|
4712
5162
|
try {
|
|
4713
|
-
const out = await
|
|
5163
|
+
const out = await invokeSchedule({ action: "export" });
|
|
4714
5164
|
downloadJson("webauto-schedules.json", out);
|
|
4715
5165
|
} catch (err) {
|
|
4716
5166
|
alert(`\u5BFC\u51FA\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4725,7 +5175,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4725
5175
|
if (!file) return;
|
|
4726
5176
|
try {
|
|
4727
5177
|
const text = await file.text();
|
|
4728
|
-
await
|
|
5178
|
+
await invokeSchedule({ action: "import", payloadJson: text, mode: "merge" });
|
|
4729
5179
|
await refreshList();
|
|
4730
5180
|
} catch (err) {
|
|
4731
5181
|
alert(`\u5BFC\u5165\u5931\u8D25: ${err?.message || String(err)}`);
|
|
@@ -4739,13 +5189,7 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4739
5189
|
return;
|
|
4740
5190
|
}
|
|
4741
5191
|
const interval = Math.max(5, Number(daemonIntervalInput.value || 30) || 30);
|
|
4742
|
-
const
|
|
4743
|
-
const ret = await ctx2.api.cmdSpawn({
|
|
4744
|
-
title: `schedule daemon ${interval}s`,
|
|
4745
|
-
cwd: "",
|
|
4746
|
-
args: [script, "daemon", "--interval-sec", String(interval), "--limit", "20", "--json"],
|
|
4747
|
-
groupKey: "scheduler"
|
|
4748
|
-
});
|
|
5192
|
+
const ret = await invokeSchedule({ action: "daemon-start", intervalSec: interval, limit: 20 });
|
|
4749
5193
|
daemonRunId = String(ret?.runId || "").trim();
|
|
4750
5194
|
setDaemonStatus(daemonRunId ? `daemon: \u8FD0\u884C\u4E2D (${daemonRunId})` : "daemon: \u542F\u52A8\u5931\u8D25");
|
|
4751
5195
|
}
|
|
@@ -4764,7 +5208,9 @@ function renderSchedulerPanel(root, ctx2) {
|
|
|
4764
5208
|
platformSelect.addEventListener("change", updateTaskTypeOptions);
|
|
4765
5209
|
taskTypeSelect.addEventListener("change", updatePlatformFields);
|
|
4766
5210
|
typeSelect.addEventListener("change", updateTypeFields);
|
|
5211
|
+
periodicTypeSelect.addEventListener("change", updateTypeFields);
|
|
4767
5212
|
saveBtn.onclick = () => void saveTask();
|
|
5213
|
+
runNowBtn.onclick = () => void runNowFromForm();
|
|
4768
5214
|
resetBtn.onclick = () => resetForm();
|
|
4769
5215
|
refreshBtn.onclick = () => void refreshList();
|
|
4770
5216
|
runDueBtn.onclick = () => void runDueNow();
|
|
@@ -4816,6 +5262,8 @@ var tabs = [
|
|
|
4816
5262
|
var tabsEl = document.getElementById("tabs");
|
|
4817
5263
|
var contentEl = document.getElementById("content");
|
|
4818
5264
|
var statusEl = document.getElementById("status");
|
|
5265
|
+
var appTitleEl = document.getElementById("app-title");
|
|
5266
|
+
var appVersionEl = document.getElementById("app-version");
|
|
4819
5267
|
var activeTabCleanup = null;
|
|
4820
5268
|
var mutableApi = { ...window.api || {}, settings: null };
|
|
4821
5269
|
var tabIcons = {
|
|
@@ -4891,6 +5339,22 @@ function startDesktopHeartbeat() {
|
|
|
4891
5339
|
async function loadSettings() {
|
|
4892
5340
|
await ctx.refreshSettings();
|
|
4893
5341
|
}
|
|
5342
|
+
async function applyVersionBadge() {
|
|
5343
|
+
try {
|
|
5344
|
+
if (typeof window.api?.appGetVersion !== "function") return;
|
|
5345
|
+
const info = await window.api.appGetVersion();
|
|
5346
|
+
const webauto = String(info?.webauto || "").trim();
|
|
5347
|
+
const desktop = String(info?.desktop || "").trim();
|
|
5348
|
+
const badge = String(info?.badge || "").trim();
|
|
5349
|
+
if (appTitleEl && webauto) {
|
|
5350
|
+
appTitleEl.textContent = `WebAuto Console v${webauto}`;
|
|
5351
|
+
}
|
|
5352
|
+
if (appVersionEl) {
|
|
5353
|
+
appVersionEl.textContent = badge || (desktop && desktop !== webauto ? `webauto v${webauto} \xB7 console v${desktop}` : webauto ? `v${webauto}` : "v-");
|
|
5354
|
+
}
|
|
5355
|
+
} catch {
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
4894
5358
|
function focusTabButton(tabId) {
|
|
4895
5359
|
const button = tabsEl.querySelector(`[data-tab-id="${tabId}"]`);
|
|
4896
5360
|
button?.focus();
|
|
@@ -4998,6 +5462,7 @@ async function detectStartupTab() {
|
|
|
4998
5462
|
}
|
|
4999
5463
|
async function main() {
|
|
5000
5464
|
startDesktopHeartbeat();
|
|
5465
|
+
await applyVersionBadge();
|
|
5001
5466
|
await loadSettings();
|
|
5002
5467
|
installCmdEvents();
|
|
5003
5468
|
const startupTab = await detectStartupTab();
|