dsh-github-flow 0.1.7 → 0.1.8

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/lib/client.js CHANGED
@@ -804,6 +804,131 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
804
804
  margin-top: 10px;
805
805
  line-height: 1.4;
806
806
  }
807
+
808
+ /* 16. 数据同步设置弹窗 (Sync Settings Modal) */
809
+ .dsh-github-modal-overlay {
810
+ position: fixed;
811
+ top: 0;
812
+ left: 0;
813
+ right: 0;
814
+ bottom: 0;
815
+ background: rgba(0, 0, 0, 0.45);
816
+ display: flex;
817
+ align-items: center;
818
+ justify-content: center;
819
+ z-index: 99999;
820
+ backdrop-filter: blur(2px);
821
+ }
822
+ .dsh-github-modal-dialog {
823
+ width: 90%;
824
+ max-width: 480px;
825
+ background: var(--dsw-alias-bg-base, #ffffff);
826
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
827
+ border-radius: 12px;
828
+ box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
829
+ padding: 24px;
830
+ box-sizing: border-box;
831
+ color: var(--dsw-alias-label-primary, #1f2328);
832
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
833
+ }
834
+ body[data-ds-dark-theme] .dsh-github-modal-dialog {
835
+ background: var(--dsw-alias-bg-layer-2, #1e1e22);
836
+ border-color: var(--dsw-alias-border-l1, #30363d);
837
+ box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45);
838
+ }
839
+ .dsh-github-radio-card {
840
+ display: flex;
841
+ align-items: flex-start;
842
+ gap: 10px;
843
+ padding: 10px 12px;
844
+ border-radius: 8px;
845
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
846
+ margin-bottom: 8px;
847
+ cursor: pointer;
848
+ transition: all 0.15s ease;
849
+ background: var(--dsw-alias-bg-layer-1, #f6f8fa);
850
+ }
851
+ .dsh-github-radio-card:hover {
852
+ border-color: var(--dsw-alias-link, #0969da);
853
+ }
854
+ .dsh-github-radio-card.active {
855
+ border-color: var(--dsw-alias-link, #0969da);
856
+ background: rgba(9, 105, 218, 0.08);
857
+ }
858
+ body[data-ds-dark-theme] .dsh-github-radio-card {
859
+ background: var(--dsw-alias-bg-base, #161b22);
860
+ border-color: var(--dsw-alias-border-l1, #30363d);
861
+ }
862
+ body[data-ds-dark-theme] .dsh-github-radio-card.active {
863
+ border-color: #58a6ff;
864
+ background: rgba(88, 166, 255, 0.12);
865
+ }
866
+
867
+ /* 下拉菜单及选项在深色/浅色模式下的精准适配 */
868
+ .dsh-github-select {
869
+ width: 100%;
870
+ padding: 8px 12px;
871
+ border-radius: 6px;
872
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
873
+ background: var(--dsw-alias-bg-base, #ffffff);
874
+ color: var(--dsw-alias-label-primary, #1f2328);
875
+ font-size: 13px;
876
+ outline: none;
877
+ cursor: pointer;
878
+ box-sizing: border-box;
879
+ color-scheme: light;
880
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
881
+ }
882
+ .dsh-github-select:focus {
883
+ border-color: var(--dsw-alias-link, #0969da);
884
+ box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
885
+ }
886
+ .dsh-github-select option {
887
+ background-color: #ffffff;
888
+ color: #1f2328;
889
+ padding: 6px 10px;
890
+ }
891
+ body[data-ds-dark-theme] .dsh-github-select {
892
+ border-color: var(--dsw-alias-border-l1, #30363d);
893
+ background: var(--dsw-alias-bg-layer-2, #1e1e22);
894
+ color: var(--dsw-alias-label-primary, #f0f6fc);
895
+ color-scheme: dark;
896
+ }
897
+ body[data-ds-dark-theme] .dsh-github-select:focus {
898
+ border-color: #58a6ff;
899
+ box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
900
+ }
901
+ body[data-ds-dark-theme] .dsh-github-select option {
902
+ background-color: #1e1e22;
903
+ color: #f0f6fc;
904
+ }
905
+
906
+ /* 缓存信息展示框 */
907
+ .dsh-github-cache-info-box {
908
+ padding: 10px 12px;
909
+ border-radius: 6px;
910
+ background: var(--dsw-alias-bg-layer-1, #f6f8fa);
911
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
912
+ margin-bottom: 20px;
913
+ font-size: 12px;
914
+ color: var(--dsw-alias-label-secondary, #656d76);
915
+ transition: all 0.15s ease;
916
+ }
917
+ body[data-ds-dark-theme] .dsh-github-cache-info-box {
918
+ background: var(--dsw-alias-bg-base, #161b22);
919
+ border-color: var(--dsw-alias-border-l1, #30363d);
920
+ color: var(--dsw-alias-label-secondary, #8c8c8c);
921
+ }
922
+
923
+ /* 单选框点强调色 */
924
+ .dsh-github-radio {
925
+ accent-color: var(--dsw-alias-link, #0969da);
926
+ cursor: pointer;
927
+ margin-top: 3px;
928
+ }
929
+ body[data-ds-dark-theme] .dsh-github-radio {
930
+ accent-color: #58a6ff;
931
+ }
807
932
  `;
808
933
  function injectStyles() {
809
934
  var styleId = "dsh-plugin-github-styles";
@@ -949,7 +1074,25 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
949
1074
  }, "🌐 官网安装包 (.msi/.pkg)")
950
1075
  : null, h("button", {
951
1076
  className: "dsh-github-action-btn",
952
- onClick: onRefresh,
1077
+ onClick: function () {
1078
+ setToastMsg("正在快速检测登录状态...");
1079
+ fetch("/api/github/auth?force=true")
1080
+ .then(function (res) { return res.json(); })
1081
+ .then(function (json) {
1082
+ if (json && json.ok && json.auth && json.auth.loggedIn) {
1083
+ setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
1084
+ clearStoredGlobalOverview();
1085
+ onRefresh();
1086
+ }
1087
+ else {
1088
+ setToastMsg("⚠️ 暂未检测到登录凭据,请在终端执行 gh auth login 完成授权");
1089
+ onRefresh();
1090
+ }
1091
+ })
1092
+ .catch(function () {
1093
+ onRefresh();
1094
+ });
1095
+ },
953
1096
  title: "重新检测登录与安装状态"
954
1097
  }, "🔄 刷新状态")), toastMsg
955
1098
  ? h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-link, #0969da)", marginTop: "8px" } }, `ℹ️ ${toastMsg}`)
@@ -957,14 +1100,254 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
957
1100
  ? "💡 提示:使用包管理器安装完成后,若未立即识别,可重启 DeepSeek Harness 应用刷新系统环境变量 PATH。"
958
1101
  : "💡 提示:凭证由官方 GitHub CLI 加密保存在操作系统安全凭据库中,授权完毕切回本窗口将自动识别刷新。"));
959
1102
  }
1103
+ // 通用升级通知与一键升级卡片组件(自适应右侧栏紧凑模式与全景大面板模式)
1104
+ function UpgradeNotificationCard(props) {
1105
+ var updateInfo = props.updateInfo;
1106
+ var isRightbar = Boolean(props.isRightbar);
1107
+ if (!updateInfo || !updateInfo.hasUpdate)
1108
+ return null;
1109
+ var storageKey = "dsh_dismissed_update_" + (updateInfo.latestVersion || "latest");
1110
+ var [dismissed, setDismissed] = React.useState(function () {
1111
+ try {
1112
+ return localStorage.getItem(storageKey) === "true";
1113
+ }
1114
+ catch (e) {
1115
+ return false;
1116
+ }
1117
+ });
1118
+ var [status, setStatus] = React.useState("idle"); // 'idle' | 'upgrading' | 'success' | 'failed'
1119
+ var [msg, setMsg] = React.useState("");
1120
+ // 窗口切回或切 Tab 重挂载时,主动对齐后端实际的升级状态,防止状态丢失
1121
+ React.useEffect(function () {
1122
+ fetch("/api/github/upgrade-status")
1123
+ .then(function (res) { return res.json(); })
1124
+ .then(function (json) {
1125
+ if (json && json.ok && json.data) {
1126
+ if (json.data.state === "upgrading") {
1127
+ setStatus("upgrading");
1128
+ setMsg("正在拉取最新版本(约需 5~15 秒)...");
1129
+ }
1130
+ else if (json.data.state === "success") {
1131
+ setStatus("success");
1132
+ setMsg(json.data.message || "升级成功!已安装最新依赖。");
1133
+ }
1134
+ }
1135
+ })
1136
+ .catch(function () { });
1137
+ }, []);
1138
+ if (dismissed)
1139
+ return null;
1140
+ var handleUpgrade = async function () {
1141
+ if (status === "upgrading")
1142
+ return;
1143
+ setStatus("upgrading");
1144
+ setMsg("正在拉取最新版本(约需 5~15 秒)...");
1145
+ try {
1146
+ var res = await fetch("/api/github/upgrade", { method: "POST" });
1147
+ var json = await res.json().catch(function () { return {}; });
1148
+ if (res.ok && json.ok) {
1149
+ setStatus("success");
1150
+ setMsg(json.message || "升级成功!已安装最新依赖。");
1151
+ }
1152
+ else {
1153
+ setStatus("failed");
1154
+ setMsg(json.message || "升级命令执行失败,请在宿主终端手动执行。");
1155
+ }
1156
+ }
1157
+ catch (err) {
1158
+ setStatus("failed");
1159
+ setMsg("无法连接后端执行升级: " + (err.message || err));
1160
+ }
1161
+ };
1162
+ var handleDismiss = function () {
1163
+ try {
1164
+ localStorage.setItem(storageKey, "true");
1165
+ }
1166
+ catch (e) { }
1167
+ setDismissed(true);
1168
+ };
1169
+ var handleCopy = function () {
1170
+ var cmd = updateInfo.upgradeCommand || "pnpm dsh plugin add dsh-github-flow@latest";
1171
+ if (navigator.clipboard) {
1172
+ navigator.clipboard.writeText(cmd);
1173
+ alert("已复制升级命令到剪贴板:\n" + cmd);
1174
+ }
1175
+ else {
1176
+ prompt("请复制升级命令:", cmd);
1177
+ }
1178
+ };
1179
+ var handleReload = function () {
1180
+ window.location.reload();
1181
+ };
1182
+ var h = React.createElement;
1183
+ // 1. 右侧栏紧凑型模式
1184
+ if (isRightbar) {
1185
+ return h("div", {
1186
+ className: "dsh-github-upgrade-strip",
1187
+ style: {
1188
+ padding: "10px 12px",
1189
+ marginBottom: "12px",
1190
+ borderRadius: "8px",
1191
+ background: status === "success"
1192
+ ? "var(--dsw-alias-state-success-subtle, rgba(46, 160, 67, 0.12))"
1193
+ : "var(--dsw-alias-bg-module-platform, rgba(9, 105, 218, 0.08))",
1194
+ border: "1px solid " + (status === "success"
1195
+ ? "var(--dsw-alias-state-success-border, rgba(46, 160, 67, 0.35))"
1196
+ : "var(--dsw-alias-border-l2, rgba(9, 105, 218, 0.25))"),
1197
+ fontSize: "12px",
1198
+ },
1199
+ }, h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" } }, h("span", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success" ? "🎉 升级完成" : "🚀 发现新版 v" + updateInfo.latestVersion), h("button", {
1200
+ onClick: handleDismiss,
1201
+ title: "不再提醒此版本",
1202
+ style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "14px", padding: 0 },
1203
+ }, "✕")), h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "8px", lineHeight: "1.4" } }, msg || (status === "success"
1204
+ ? "需重启 DSH 服务彻底生效后端工具,或刷新网页预览新界面。"
1205
+ : "当前: v" + updateInfo.currentVersion + ",推荐升级以获取最新功能。")), h("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" } }, status === "success"
1206
+ ? h("button", {
1207
+ className: "dsh-github-btn-sm",
1208
+ onClick: handleReload,
1209
+ style: { flex: 1, justifyContent: "center", background: "#2ea043", color: "#fff", borderColor: "#2ea043" },
1210
+ }, "🔄 刷新页面")
1211
+ : h("button", {
1212
+ className: "dsh-github-btn-sm",
1213
+ disabled: status === "upgrading",
1214
+ onClick: handleUpgrade,
1215
+ style: {
1216
+ flex: 1,
1217
+ justifyContent: "center",
1218
+ background: "var(--dsw-alias-link, #0969da)",
1219
+ color: "#fff",
1220
+ borderColor: "var(--dsw-alias-link, #0969da)",
1221
+ opacity: status === "upgrading" ? 0.7 : 1,
1222
+ },
1223
+ }, status === "upgrading" ? "升级中..." : "⚡ 一键升级"), h("button", {
1224
+ className: "dsh-github-btn-sm",
1225
+ onClick: handleCopy,
1226
+ style: { fontSize: "11px", padding: "4px 8px" },
1227
+ }, "📋 复制命令")));
1228
+ }
1229
+ // 2. 全景驾驶舱大面板横幅模式
1230
+ return h("div", {
1231
+ className: "dsh-github-upgrade-banner",
1232
+ style: {
1233
+ display: "flex",
1234
+ alignItems: "center",
1235
+ justifyContent: "space-between",
1236
+ padding: "12px 18px",
1237
+ marginBottom: "16px",
1238
+ borderRadius: "8px",
1239
+ background: status === "success"
1240
+ ? "linear-gradient(90deg, rgba(46, 160, 67, 0.15) 0%, rgba(9, 105, 218, 0.12) 100%)"
1241
+ : "linear-gradient(90deg, rgba(9, 105, 218, 0.12) 0%, rgba(137, 87, 229, 0.12) 100%)",
1242
+ border: "1px solid " + (status === "success" ? "rgba(46, 160, 67, 0.35)" : "rgba(9, 105, 218, 0.3)"),
1243
+ fontSize: "13px",
1244
+ },
1245
+ }, h("div", { style: { display: "flex", alignItems: "center", gap: "10px" } }, h("span", { style: { fontSize: "20px" } }, status === "success" ? "🎉" : "🚀"), h("div", null, h("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success"
1246
+ ? "升级完成!新版本 v" + updateInfo.latestVersion + " 已写入"
1247
+ : "发现 dsh-github-flow 新版本 v" + updateInfo.latestVersion + "(当前版本: v" + updateInfo.currentVersion + ")"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, msg || (status === "success"
1248
+ ? "💡 提示:DSH 后端驻留了旧版内存,需重启 DSH 服务彻底生效后端工具;若仅预览界面改动可先刷新网页。"
1249
+ : "包含最新 GitHub 工具特性与性能优化,点击右侧可一键自动升级。")))), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 } }, status === "success"
1250
+ ? h("button", {
1251
+ className: "dsh-github-btn-sm",
1252
+ onClick: handleReload,
1253
+ style: { background: "#2ea043", color: "#fff", borderColor: "#2ea043", padding: "6px 14px", fontWeight: 600 },
1254
+ }, "🔄 立即刷新页面")
1255
+ : h("button", {
1256
+ className: "dsh-github-btn-sm",
1257
+ disabled: status === "upgrading",
1258
+ onClick: handleUpgrade,
1259
+ style: {
1260
+ background: "var(--dsw-alias-link, #0969da)",
1261
+ color: "#fff",
1262
+ borderColor: "var(--dsw-alias-link, #0969da)",
1263
+ padding: "6px 14px",
1264
+ fontWeight: 600,
1265
+ opacity: status === "upgrading" ? 0.7 : 1,
1266
+ },
1267
+ }, status === "upgrading" ? "正在自动升级中..." : "⚡ 一键自动升级"), h("button", {
1268
+ className: "dsh-github-btn-sm",
1269
+ onClick: handleCopy,
1270
+ style: { padding: "6px 12px" },
1271
+ }, "📋 复制命令"), h("button", {
1272
+ onClick: handleDismiss,
1273
+ title: "不再提醒此版本",
1274
+ style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
1275
+ }, "✕")));
1276
+ }
1277
+ // 数据新鲜度与同步状态微标组件 (自适应全景大面板与右侧栏紧凑模式)
1278
+ function SyncStatusBadge(props) {
1279
+ var syncStatus = props.syncStatus;
1280
+ var lastSyncedAt = props.lastSyncedAt;
1281
+ var isRightbar = Boolean(props.isRightbar);
1282
+ var text = "";
1283
+ var bg = "rgba(101, 109, 118, 0.1)";
1284
+ var color = "var(--dsw-alias-label-secondary, #656d76)";
1285
+ var formatTimeAgo = function (ts) {
1286
+ if (!ts)
1287
+ return "刚刚";
1288
+ var diffSec = Math.floor((Date.now() - ts) / 1000);
1289
+ if (diffSec < 60)
1290
+ return "刚刚";
1291
+ var diffMin = Math.floor(diffSec / 60);
1292
+ if (diffMin < 60)
1293
+ return diffMin + "分钟前";
1294
+ var diffHour = Math.floor(diffMin / 60);
1295
+ if (diffHour < 24)
1296
+ return diffHour + "小时前";
1297
+ var diffDay = Math.floor(diffHour / 24);
1298
+ return diffDay + "天前";
1299
+ };
1300
+ if (syncStatus === "syncing") {
1301
+ text = isRightbar ? "⏳ 同步中..." : "⏳ 后台同步中...";
1302
+ bg = "rgba(9, 105, 218, 0.12)";
1303
+ color = "var(--dsw-alias-link, #0969da)";
1304
+ }
1305
+ else if (syncStatus === "fresh") {
1306
+ var timeStr = lastSyncedAt ? new Date(lastSyncedAt).toLocaleTimeString() : "";
1307
+ text = "✅ " + (isRightbar ? "最新" : "已是最新") + (timeStr ? " (" + timeStr + ")" : "");
1308
+ bg = "rgba(46, 160, 67, 0.12)";
1309
+ color = "var(--dsw-alias-state-success, #2ea043)";
1310
+ }
1311
+ else if (syncStatus === "cached") {
1312
+ text = "📦 本地快照 (" + formatTimeAgo(lastSyncedAt) + ")";
1313
+ bg = "rgba(101, 109, 118, 0.12)";
1314
+ color = "var(--dsw-alias-label-secondary, #656d76)";
1315
+ }
1316
+ else if (syncStatus === "failed") {
1317
+ text = isRightbar ? "⚠️ 同步超时" : "⚠️ 同步超时,已保留本地缓存";
1318
+ bg = "rgba(207, 34, 46, 0.1)";
1319
+ color = "var(--dsw-alias-state-error-primary, #cf222e)";
1320
+ }
1321
+ if (!text)
1322
+ return null;
1323
+ return React.createElement("span", {
1324
+ className: "dsh-github-sync-badge",
1325
+ style: {
1326
+ fontSize: isRightbar ? "10px" : "11px",
1327
+ padding: isRightbar ? "2px 6px" : "3px 8px",
1328
+ borderRadius: "12px",
1329
+ background: bg,
1330
+ color: color,
1331
+ marginLeft: isRightbar ? "6px" : "10px",
1332
+ fontWeight: 500,
1333
+ display: "inline-flex",
1334
+ alignItems: "center",
1335
+ whiteSpace: "nowrap",
1336
+ },
1337
+ }, text);
1338
+ }
960
1339
  // 渲染 GitHub 仓库信息与操作视图的核心函数
961
- function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false) {
1340
+ function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0) {
962
1341
  var auth = data ? data.auth : null;
963
1342
  var repo = data ? data.repo : null;
964
1343
  var prs = (data && data.pullRequests) || [];
965
1344
  var issues = (data && data.issues) || [];
966
1345
  var runs = (data && data.runs) || [];
967
- var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台")), h("button", {
1346
+ var effectiveUpdateInfo = updateInfo || data?.updateInfo;
1347
+ var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
1348
+ ? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
1349
+ : null;
1350
+ var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台"), syncStatus ? h(SyncStatusBadge, { syncStatus, lastSyncedAt, isRightbar }) : null), h("button", {
968
1351
  className: "dsh-github-refresh-btn",
969
1352
  onClick: onRefresh,
970
1353
  disabled: loading,
@@ -1075,23 +1458,202 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
1075
1458
  var authGuideEl = !auth?.loggedIn
1076
1459
  ? h(AuthGuideCard, { auth, onRefresh, isRightbar })
1077
1460
  : null;
1078
- return [headerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
1461
+ return [headerEl, upgradeBannerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
1462
+ }
1463
+ // 全局持久化缓存与偏好配置键名
1464
+ var GLOBAL_OVERVIEW_CACHE_KEY = "dsh_github_global_overview_snapshot_v1";
1465
+ var SYNC_SETTINGS_STORAGE_KEY = "dsh_github_sync_settings_v1";
1466
+ var DEFAULT_SYNC_SETTINGS = {
1467
+ strategy: "swr", // 'swr' (秒开展示旧数据,后台静默拉取) | 'manual' (仅手动刷新) | 'interval' (定时轮询)
1468
+ intervalMs: 300000, // 默认 5 分钟 (300,000ms)
1469
+ };
1470
+ function getLocalSyncSettings() {
1471
+ try {
1472
+ var raw = localStorage.getItem(SYNC_SETTINGS_STORAGE_KEY);
1473
+ if (!raw)
1474
+ return DEFAULT_SYNC_SETTINGS;
1475
+ var parsed = JSON.parse(raw);
1476
+ return Object.assign({}, DEFAULT_SYNC_SETTINGS, parsed);
1477
+ }
1478
+ catch (e) {
1479
+ return DEFAULT_SYNC_SETTINGS;
1480
+ }
1481
+ }
1482
+ function saveLocalSyncSettings(settings) {
1483
+ try {
1484
+ localStorage.setItem(SYNC_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
1485
+ // 跨组件事件通知:确保主页设置被修改后,右侧栏及所有实例即时同步新规则
1486
+ if (typeof window !== "undefined") {
1487
+ window.dispatchEvent(new CustomEvent("dsh-github-sync-settings-change", { detail: settings }));
1488
+ }
1489
+ }
1490
+ catch (e) { }
1491
+ }
1492
+ function getStoredGlobalOverview() {
1493
+ try {
1494
+ var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
1495
+ if (!raw)
1496
+ return null;
1497
+ var parsed = JSON.parse(raw);
1498
+ // 毒丸缓存自愈清洗:如果缓存中的快照显示「未登录」或未就绪,立即将其从 localStorage 彻底抹除!
1499
+ if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
1500
+ localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
1501
+ return null;
1502
+ }
1503
+ if (typeof parsed.savedAt === "number") {
1504
+ return parsed;
1505
+ }
1506
+ return null;
1507
+ }
1508
+ catch (e) {
1509
+ return null;
1510
+ }
1511
+ }
1512
+ function saveStoredGlobalOverview(data) {
1513
+ // 毒丸拦截守卫:绝不持久化存储未登录状态、空数据或异常快照!
1514
+ if (!data || !data.auth || !data.auth.loggedIn) {
1515
+ return;
1516
+ }
1517
+ try {
1518
+ localStorage.setItem(GLOBAL_OVERVIEW_CACHE_KEY, JSON.stringify({
1519
+ data: data,
1520
+ savedAt: Date.now(),
1521
+ }));
1522
+ }
1523
+ catch (e) { }
1524
+ }
1525
+ function clearStoredGlobalOverview() {
1526
+ try {
1527
+ localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
1528
+ // 彻底清空所有右侧栏工作区历史快照,确保全局一致
1529
+ if (typeof localStorage !== "undefined") {
1530
+ for (var i = localStorage.length - 1; i >= 0; i--) {
1531
+ var k = localStorage.key(i);
1532
+ if (k && k.indexOf("dsh_github_rightbar_snapshot_") === 0) {
1533
+ localStorage.removeItem(k);
1534
+ }
1535
+ }
1536
+ }
1537
+ if (typeof window !== "undefined") {
1538
+ window.dispatchEvent(new CustomEvent("dsh-github-cache-cleared"));
1539
+ }
1540
+ }
1541
+ catch (e) { }
1542
+ }
1543
+ function getCacheStorageMeta() {
1544
+ try {
1545
+ var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
1546
+ if (!raw)
1547
+ return { exists: false, sizeKb: 0, savedAt: 0 };
1548
+ var parsed = JSON.parse(raw);
1549
+ var sizeKb = Math.round((raw.length * 2) / 1024 * 10) / 10;
1550
+ return { exists: true, sizeKb: sizeKb, savedAt: parsed?.savedAt || 0 };
1551
+ }
1552
+ catch (e) {
1553
+ return { exists: false, sizeKb: 0, savedAt: 0 };
1554
+ }
1555
+ }
1556
+ // 右侧栏专属本地持久化快照工具
1557
+ function getRightbarStorageKey(cwd) {
1558
+ var safe = (cwd || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
1559
+ return "dsh_github_rightbar_snapshot_" + safe;
1560
+ }
1561
+ function getStoredRightbarSnapshot(cwd) {
1562
+ try {
1563
+ var key = getRightbarStorageKey(cwd);
1564
+ var raw = localStorage.getItem(key);
1565
+ if (!raw)
1566
+ return null;
1567
+ var parsed = JSON.parse(raw);
1568
+ // 毒丸拦截:若快照未登录,物理删除
1569
+ if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
1570
+ localStorage.removeItem(key);
1571
+ return null;
1572
+ }
1573
+ return { data: parsed.data, savedAt: typeof parsed.savedAt === "number" ? parsed.savedAt : 0 };
1574
+ }
1575
+ catch (e) {
1576
+ return null;
1577
+ }
1578
+ }
1579
+ function saveStoredRightbarData(cwd, data) {
1580
+ if (!data || !data.auth || !data.auth.loggedIn)
1581
+ return;
1582
+ try {
1583
+ var key = getRightbarStorageKey(cwd);
1584
+ localStorage.setItem(key, JSON.stringify({
1585
+ data: data,
1586
+ savedAt: Date.now(),
1587
+ }));
1588
+ }
1589
+ catch (e) { }
1079
1590
  }
1080
1591
  // 1. 右侧栏专属视图 (Right Sidebar Tab Body)
1081
1592
  function GithubRightbarBody(props) {
1082
- var [loading, setLoading] = React.useState(true);
1083
- var [data, setData] = React.useState(null);
1084
- var [error, setError] = React.useState(null);
1085
- // 核心:直接从会话 Props 拿到真实物理工作区路径!
1593
+ var sessionId = props ? props.sessionId : null;
1086
1594
  var cwd = null;
1087
- if (props && props.useSessions && props.sessionId) {
1595
+ if (props && typeof props.useSessions === "function" && sessionId) {
1088
1596
  try {
1089
- cwd = props.useSessions((sessions) => sessions?.byId?.[props.sessionId]?.cwd);
1597
+ cwd = props.useSessions((sessions) => sessions?.byId?.[sessionId]?.cwd) || null;
1090
1598
  }
1091
1599
  catch (e) { }
1092
1600
  }
1601
+ // 核心原则:右侧栏与全景主页使用同一套在“主页-设置”中配置的缓存与刷新规则事实源
1602
+ var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
1603
+ var [syncSettings, setSyncSettings] = React.useState(initialSettings);
1604
+ // SWR 秒开机制:初始数据直接从本地快照读取,0ms 瞬间展示!
1605
+ var initialSnapshot = React.useMemo(() => getStoredRightbarSnapshot(cwd), [cwd]);
1606
+ var [data, setData] = React.useState(initialSnapshot?.data || null);
1607
+ var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
1608
+ var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
1609
+ var [loading, setLoading] = React.useState(false);
1610
+ var [error, setError] = React.useState(null);
1611
+ var [updateInfo, setUpdateInfo] = React.useState(null);
1612
+ // 跨组件事件监听:当主页修改了同步设置或清空缓存时,右侧栏即时无感对齐
1613
+ React.useEffect(function () {
1614
+ var onSettingsChange = function (e) {
1615
+ if (e && e.detail) {
1616
+ setSyncSettings(e.detail);
1617
+ }
1618
+ };
1619
+ var onCacheCleared = function () {
1620
+ setData(null);
1621
+ setLastSyncedAt(0);
1622
+ setSyncStatus("loading");
1623
+ };
1624
+ window.addEventListener("dsh-github-sync-settings-change", onSettingsChange);
1625
+ window.addEventListener("dsh-github-cache-cleared", onCacheCleared);
1626
+ return function () {
1627
+ window.removeEventListener("dsh-github-sync-settings-change", onSettingsChange);
1628
+ window.removeEventListener("dsh-github-cache-cleared", onCacheCleared);
1629
+ };
1630
+ }, []);
1631
+ // gh 数据拉取完毕后,闲时 1.5 秒异步检测升级,绝对不抢占主业务首屏资源
1632
+ React.useEffect(() => {
1633
+ if (!data)
1634
+ return;
1635
+ var timer = setTimeout(function () {
1636
+ fetch("/api/github/check-update")
1637
+ .then(function (res) { return res.json(); })
1638
+ .then(function (json) {
1639
+ if (json && json.ok && json.data) {
1640
+ setUpdateInfo(json.data);
1641
+ }
1642
+ })
1643
+ .catch(function () { });
1644
+ }, 1500);
1645
+ return function () { clearTimeout(timer); };
1646
+ }, [data]);
1647
+ // 遵循全局规则的数据拉取核心函数 (仅依赖标量 cwd 与 sessionId,坚决去除 props 引用依赖,彻底终结死循环)
1093
1648
  var loadData = React.useCallback(async (forceRefresh = false) => {
1094
- setLoading(true);
1649
+ // 规则执行:若主页设置为「纯手动模式 (manual)」,且非用户点击强制刷新,且本地已有数据,坚决不打后台网络!
1650
+ if (!forceRefresh && syncSettings.strategy === "manual" && data) {
1651
+ return;
1652
+ }
1653
+ if (forceRefresh || !data) {
1654
+ setLoading(true);
1655
+ }
1656
+ setSyncStatus(data ? "syncing" : "loading");
1095
1657
  setError(null);
1096
1658
  try {
1097
1659
  if (forceRefresh) {
@@ -1099,8 +1661,8 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
1099
1661
  }
1100
1662
  var url = "/api/github/overview";
1101
1663
  var params = [];
1102
- if (props && props.sessionId) {
1103
- params.push("sessionId=" + encodeURIComponent(props.sessionId));
1664
+ if (sessionId) {
1665
+ params.push("sessionId=" + encodeURIComponent(sessionId));
1104
1666
  }
1105
1667
  if (cwd) {
1106
1668
  params.push("cwd=" + encodeURIComponent(cwd));
@@ -1111,39 +1673,82 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
1111
1673
  var res = await fetch(url);
1112
1674
  if (!res.ok) {
1113
1675
  var errText = await res.text().catch(() => "");
1114
- setError(`后端服务未就绪 (${res.status}): ${errText || res.statusText}`);
1676
+ var msg = `后端服务未就绪 (${res.status}): ${errText || res.statusText}`;
1677
+ if (data) {
1678
+ setSyncStatus("failed");
1679
+ }
1680
+ else {
1681
+ setError(msg);
1682
+ }
1115
1683
  return;
1116
1684
  }
1117
1685
  var json = await res.json();
1118
1686
  if (json.ok && json.data) {
1119
1687
  setData(json.data);
1688
+ saveStoredRightbarData(cwd, json.data); // 写入持久化快照
1689
+ setLastSyncedAt(Date.now());
1690
+ setSyncStatus("fresh");
1691
+ if (json.data.updateInfo) {
1692
+ setUpdateInfo(json.data.updateInfo);
1693
+ }
1694
+ }
1695
+ else if (data) {
1696
+ setSyncStatus("failed");
1120
1697
  }
1121
1698
  else {
1122
1699
  setError(json.error || "获取状态失败");
1123
1700
  }
1124
1701
  }
1125
1702
  catch (err) {
1126
- setError(err.message || "网络异常");
1703
+ if (data) {
1704
+ setSyncStatus("failed");
1705
+ }
1706
+ else {
1707
+ setError(err.message || "网络异常");
1708
+ }
1127
1709
  }
1128
1710
  finally {
1129
1711
  setLoading(false);
1130
1712
  }
1131
- }, [cwd, props]);
1713
+ }, [cwd, sessionId, data, syncSettings.strategy]);
1714
+ // 挂载与策略调度:与主页规则 100% 保持一致
1715
+ React.useEffect(() => {
1716
+ if (syncSettings.strategy === "swr") {
1717
+ var timer = setTimeout(function () { loadData(false); }, 300);
1718
+ return function () { clearTimeout(timer); };
1719
+ }
1720
+ else if (!data) {
1721
+ loadData(false);
1722
+ }
1723
+ }, [cwd, sessionId, syncSettings.strategy]);
1724
+ // 定时轮询调度:与主页完全一致
1132
1725
  React.useEffect(() => {
1133
- loadData(false);
1134
- }, [loadData]);
1135
- // 窗口切回自动探测自愈
1726
+ if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
1727
+ return;
1728
+ var timer = setInterval(function () {
1729
+ loadData(false);
1730
+ }, syncSettings.intervalMs);
1731
+ return function () { clearInterval(timer); };
1732
+ }, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
1733
+ // 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
1136
1734
  React.useEffect(() => {
1137
1735
  var onFocus = () => {
1138
1736
  if (!data?.auth?.loggedIn) {
1139
- loadData(false);
1737
+ fetch("/api/github/auth?force=true")
1738
+ .then(function (res) { return res.json(); })
1739
+ .then(function (json) {
1740
+ if (json && json.ok && json.auth && json.auth.loggedIn) {
1741
+ loadData(true);
1742
+ }
1743
+ })
1744
+ .catch(function () { });
1140
1745
  }
1141
1746
  };
1142
1747
  window.addEventListener("focus", onFocus);
1143
1748
  return () => window.removeEventListener("focus", onFocus);
1144
1749
  }, [data, loadData]);
1145
1750
  var h = React.createElement;
1146
- return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true));
1751
+ return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt));
1147
1752
  }
1148
1753
  // 2. 右侧栏 Tab 标题组件
1149
1754
  function GithubRightbarTitle() {
@@ -1164,76 +1769,282 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
1164
1769
  className: "dsh-github-icon-inner" + (active ? " active" : "")
1165
1770
  }, renderOctocat(18, "currentColor")));
1166
1771
  }
1772
+ // 全景驾驶舱「⚙️ 数据同步与缓存设置」模态弹窗组件
1773
+ function GlobalSyncSettingsModal(props) {
1774
+ if (!props.isOpen)
1775
+ return null;
1776
+ var [strategy, setStrategy] = React.useState(props.currentSettings.strategy || "swr");
1777
+ var [intervalMs, setIntervalMs] = React.useState(props.currentSettings.intervalMs || 300000);
1778
+ var [cacheMeta, setCacheMeta] = React.useState(getCacheStorageMeta());
1779
+ var [clearTip, setClearTip] = React.useState("");
1780
+ // 键盘 Escape 快捷关闭支持
1781
+ React.useEffect(function () {
1782
+ var onKeyDown = function (e) {
1783
+ if (e.key === "Escape" || e.keyCode === 27) {
1784
+ props.onClose();
1785
+ }
1786
+ };
1787
+ window.addEventListener("keydown", onKeyDown);
1788
+ return function () { window.removeEventListener("keydown", onKeyDown); };
1789
+ }, [props.onClose]);
1790
+ var h = React.createElement;
1791
+ var handleSave = function () {
1792
+ var updated = {
1793
+ strategy: strategy,
1794
+ intervalMs: Number(intervalMs),
1795
+ };
1796
+ saveLocalSyncSettings(updated);
1797
+ props.onSave(updated);
1798
+ props.onClose();
1799
+ };
1800
+ var handleClear = function () {
1801
+ clearStoredGlobalOverview();
1802
+ props.onClearCache();
1803
+ setCacheMeta({ exists: false, sizeKb: 0, savedAt: 0 });
1804
+ setClearTip("本地缓存已彻底清空!");
1805
+ setTimeout(function () { setClearTip(""); }, 2000);
1806
+ };
1807
+ return h("div", { className: "dsh-github-modal-overlay", onClick: props.onClose }, h("div", {
1808
+ className: "dsh-github-modal-dialog",
1809
+ onClick: function (e) { e.stopPropagation(); },
1810
+ }, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" } }, h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 } }, "⚙️ 数据同步与缓存偏好"), h("button", {
1811
+ style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #656d76)" },
1812
+ onClick: props.onClose,
1813
+ }, "✕")), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "配置 GitHub 全景驾驶舱的数据拉取节奏。启用秒开模式可利用本地持久化缓存消除每次打开时的漫长等待。"),
1814
+ // 刷新策略选择
1815
+ h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "数据刷新策略"),
1816
+ // 策略 A: SWR 秒开
1817
+ h("div", {
1818
+ className: "dsh-github-radio-card" + (strategy === "swr" ? " active" : ""),
1819
+ onClick: function () { setStrategy("swr"); },
1820
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "swr", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⚡ 秒开展示旧数据 (SWR, 强烈推荐)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开驾驶舱立即展示上次本地缓存(0ms秒开),后台静默同步最新状态。"))),
1821
+ // 策略 B: 纯手动刷新
1822
+ h("div", {
1823
+ className: "dsh-github-radio-card" + (strategy === "manual" ? " active" : ""),
1824
+ onClick: function () { setStrategy("manual"); },
1825
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "manual", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "📋 仅在手动点击时拉取 (Manual Only)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开只浏览本地缓存,绝不自动触发后台 gh 进程,最省电、最省资源。"))),
1826
+ // 策略 C: 定时轮询
1827
+ h("div", {
1828
+ className: "dsh-github-radio-card" + (strategy === "interval" ? " active" : ""),
1829
+ onClick: function () { setStrategy("interval"); },
1830
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "interval", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⏱️ 定时自动轮询 (Auto Poll)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "驾驶舱打开期间,按照下方设定的时间间隔在后台自动轮询。")))),
1831
+ // 自动拉取间隔下拉
1832
+ h("div", { style: { marginBottom: "18px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "6px" } }, "后台自动拉取间隔"), h("select", {
1833
+ className: "dsh-github-select",
1834
+ value: intervalMs,
1835
+ onChange: function (e) { setIntervalMs(Number(e.target.value)); },
1836
+ }, h("option", { value: 60000 }, "1 分钟 (高频刷新)"), h("option", { value: 300000 }, "5 分钟 (默认推荐)"), h("option", { value: 900000 }, "15 分钟"), h("option", { value: 1800000 }, "30 分钟 (推荐节能)"), h("option", { value: 3600000 }, "1 小时"), h("option", { value: 0 }, "从不自动刷新 (仅手动)"))),
1837
+ // 本地缓存信息与清理
1838
+ h("div", {
1839
+ className: "dsh-github-cache-info-box",
1840
+ }, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, h("span", null, cacheMeta.exists
1841
+ ? `📦 本地缓存: 约 ${cacheMeta.sizeKb} KB (上次保存: ${new Date(cacheMeta.savedAt).toLocaleTimeString()})`
1842
+ : "📦 暂无本地持久化缓存"), cacheMeta.exists ? h("button", {
1843
+ className: "dsh-github-btn-sm",
1844
+ style: { padding: "3px 8px", fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #cf222e)" },
1845
+ onClick: handleClear,
1846
+ }, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null),
1847
+ // 操作按钮
1848
+ h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px" } }, h("button", {
1849
+ className: "dsh-github-btn-sm",
1850
+ style: { padding: "6px 14px" },
1851
+ onClick: props.onClose,
1852
+ }, "取消"), h("button", {
1853
+ className: "dsh-github-btn-sm",
1854
+ style: {
1855
+ padding: "6px 16px",
1856
+ background: "var(--dsw-alias-link, #0969da)",
1857
+ color: "#ffffff",
1858
+ borderColor: "var(--dsw-alias-link, #0969da)",
1859
+ fontWeight: 600,
1860
+ },
1861
+ onClick: handleSave,
1862
+ }, "保存并应用"))));
1863
+ }
1167
1864
  function GithubWorkspacePanel() {
1168
- var [loading, setLoading] = React.useState(true);
1169
- var [data, setData] = React.useState(null);
1865
+ // 1. 读取本地偏好配置与历史持久化快照 (0ms 纯内存读取)
1866
+ var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
1867
+ var initialSnapshot = React.useMemo(() => getStoredGlobalOverview(), []);
1868
+ var [syncSettings, setSyncSettings] = React.useState(initialSettings);
1869
+ var [data, setData] = React.useState(initialSnapshot?.data || null);
1870
+ var [loading, setLoading] = React.useState(!initialSnapshot);
1871
+ var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
1872
+ var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
1873
+ var [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
1170
1874
  var [error, setError] = React.useState(null);
1875
+ var [updateInfo, setUpdateInfo] = React.useState(null);
1171
1876
  var [activeTab, setActiveTab] = React.useState("workspaces"); // 'workspaces' | 'repos' | 'mywork'
1172
1877
  var [searchQuery, setSearchQuery] = React.useState("");
1173
1878
  var [issueFilter, setIssueFilter] = React.useState("all"); // 'all' | 'reported' | 'assigned' | 'created'
1879
+ // gh 业务数据就绪后,闲时 1.5 秒异步静默检测升级,绝不影响首屏
1880
+ React.useEffect(() => {
1881
+ if (!data)
1882
+ return;
1883
+ var timer = setTimeout(function () {
1884
+ fetch("/api/github/check-update")
1885
+ .then(function (res) { return res.json(); })
1886
+ .then(function (json) {
1887
+ if (json && json.ok && json.data) {
1888
+ setUpdateInfo(json.data);
1889
+ }
1890
+ })
1891
+ .catch(function () { });
1892
+ }, 1500);
1893
+ return function () { clearTimeout(timer); };
1894
+ }, [data]);
1174
1895
  var loadData = React.useCallback(async (forceRefresh = false) => {
1896
+ // 如果是纯手动模式,且非显式强制刷新,且本地已有旧数据,直接跳过后台拉取
1897
+ if (!forceRefresh && syncSettings.strategy === "manual" && data) {
1898
+ return;
1899
+ }
1175
1900
  setLoading(true);
1901
+ setSyncStatus(data ? "syncing" : "loading");
1176
1902
  setError(null);
1177
- // 全局驾驶舱要 spawn 十几次 gh,冷启动可能超过 10 秒。
1178
- // 不设显式超时的话浏览器会抛一个与真实原因无关的网络错误,
1179
- // 早先这里会把所有失败都归因成「后端服务未就绪或接口不存在」——归因错误比没有报错更糟。
1180
1903
  var controller = new AbortController();
1181
1904
  var timedOut = false;
1182
- var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 30000);
1905
+ var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
1183
1906
  try {
1184
1907
  if (forceRefresh)
1185
1908
  await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
1186
1909
  var res = await fetch("/api/github/global-overview", { signal: controller.signal });
1187
1910
  if (!res.ok) {
1188
1911
  var errText = await res.text().catch(() => "");
1189
- setError(`获取全局数据失败(HTTP ${res.status})${errText ? ": " + errText : ""}`);
1912
+ var failMsg = `获取全局数据失败(HTTP ${res.status})${errText ? ": " + errText : ""}`;
1913
+ if (data) {
1914
+ // 容灾保障:有本地旧数据时绝不红屏崩溃,平滑降级展示离线快照
1915
+ setSyncStatus("failed");
1916
+ }
1917
+ else {
1918
+ setError(failMsg);
1919
+ }
1190
1920
  return;
1191
1921
  }
1192
1922
  var json = await res.json();
1193
1923
  if (json.ok && json.data) {
1194
1924
  setData(json.data);
1195
- // 工作区矩阵单独失败时不能静默:否则界面显示「暂无工作区」,把故障伪装成空状态
1925
+ saveStoredGlobalOverview(json.data); // 写入持久化快照
1926
+ setLastSyncedAt(Date.now());
1927
+ setSyncStatus("fresh");
1928
+ if (json.data.updateInfo) {
1929
+ setUpdateInfo(json.data.updateInfo);
1930
+ }
1196
1931
  if (json.data.workspaceError) {
1197
1932
  setError("工作区列表读取失败:" + json.data.workspaceError);
1198
1933
  }
1199
1934
  }
1200
1935
  else {
1201
- setError(json.error || "获取全局数据失败");
1936
+ if (data) {
1937
+ setSyncStatus("failed");
1938
+ }
1939
+ else {
1940
+ setError(json.error || "获取全局数据失败");
1941
+ }
1202
1942
  }
1203
1943
  }
1204
1944
  catch (err) {
1205
- if (timedOut || err.name === "AbortError") {
1206
- setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
1945
+ if (data) {
1946
+ // 后台网络故障时,旧数据依然完整可用
1947
+ setSyncStatus("failed");
1207
1948
  }
1208
1949
  else {
1209
- setError("无法连接后端服务:" + (err.message || err));
1950
+ if (timedOut || err.name === "AbortError") {
1951
+ setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
1952
+ }
1953
+ else {
1954
+ setError("无法连接后端服务:" + (err.message || err));
1955
+ }
1210
1956
  }
1211
1957
  }
1212
1958
  finally {
1213
1959
  clearTimeout(timer);
1214
1960
  setLoading(false);
1215
1961
  }
1216
- }, []);
1217
- React.useEffect(() => { loadData(false); }, [loadData]);
1218
- // 窗口切回自动探测自愈
1962
+ }, [data, syncSettings]);
1963
+ // 挂载加载调度:SWR 模式下初次延迟 300ms 后台平滑发起静默同步
1964
+ React.useEffect(() => {
1965
+ if (syncSettings.strategy === "swr") {
1966
+ var timer = setTimeout(function () { loadData(false); }, 300);
1967
+ return function () { clearTimeout(timer); };
1968
+ }
1969
+ else if (!data) {
1970
+ loadData(false);
1971
+ }
1972
+ }, [syncSettings.strategy]);
1973
+ // 定时轮询调度
1974
+ React.useEffect(() => {
1975
+ if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
1976
+ return;
1977
+ var timer = setInterval(function () {
1978
+ loadData(false);
1979
+ }, syncSettings.intervalMs);
1980
+ return function () { clearInterval(timer); };
1981
+ }, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
1982
+ // 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
1219
1983
  React.useEffect(() => {
1220
1984
  var onFocus = () => {
1221
1985
  if (!data?.auth?.loggedIn) {
1222
- loadData(false);
1986
+ fetch("/api/github/auth?force=true")
1987
+ .then(function (res) { return res.json(); })
1988
+ .then(function (json) {
1989
+ if (json && json.ok && json.auth && json.auth.loggedIn) {
1990
+ clearStoredGlobalOverview();
1991
+ loadData(true);
1992
+ }
1993
+ })
1994
+ .catch(function () { });
1223
1995
  }
1224
1996
  };
1225
1997
  window.addEventListener("focus", onFocus);
1226
1998
  return () => window.removeEventListener("focus", onFocus);
1227
1999
  }, [data, loadData]);
1228
2000
  var h = React.createElement;
1229
- var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱")), h("button", {
2001
+ var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), h(SyncStatusBadge, { syncStatus, lastSyncedAt })), h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("button", {
1230
2002
  className: "dsh-github-refresh-btn",
1231
2003
  onClick: () => loadData(true),
1232
2004
  disabled: loading,
1233
- }, loading ? "刷新中..." : "🔄 刷新全局数据"));
2005
+ }, loading ? "同步中..." : "🔄 刷新全局数据"), h("button", {
2006
+ className: "dsh-github-btn-sm",
2007
+ onClick: () => setIsSettingsModalOpen(true),
2008
+ title: "配置数据拉取节奏与本地缓存偏好",
2009
+ style: { padding: "6px 12px" },
2010
+ }, "⚙️ 同步设置")));
1234
2011
  if (loading && !data) {
1235
2012
  return h("div", { className: "dsh-github-panel" }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
1236
2013
  }
2014
+ var effectiveUpdateInfo = updateInfo || data?.updateInfo;
2015
+ var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
2016
+ ? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar: false })
2017
+ : null;
2018
+ var settingsModalEl = h(GlobalSyncSettingsModal, {
2019
+ isOpen: isSettingsModalOpen,
2020
+ onClose: () => setIsSettingsModalOpen(false),
2021
+ currentSettings: syncSettings,
2022
+ onSave: (newSettings) => {
2023
+ setSyncSettings(newSettings);
2024
+ },
2025
+ onClearCache: () => {
2026
+ setSyncStatus("loading");
2027
+ setData(null);
2028
+ setLastSyncedAt(0);
2029
+ },
2030
+ });
2031
+ // 错误拦截阻断:若无本地数据且请求失败,直接呈现错误卡片,杜绝把接口异常伪装成「未安装 CLI」
2032
+ if (!data && error) {
2033
+ return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, h("div", {
2034
+ style: {
2035
+ padding: "24px",
2036
+ borderRadius: "8px",
2037
+ background: "rgba(248, 81, 73, 0.08)",
2038
+ border: "1px solid rgba(248, 81, 73, 0.3)",
2039
+ margin: "16px 0",
2040
+ textAlign: "center",
2041
+ },
2042
+ }, h("div", { style: { fontSize: "16px", fontWeight: 600, color: "#f85149", marginBottom: "8px" } }, "⚠️ 后端服务加载异常"), h("div", { style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "16px" } }, error), h("button", {
2043
+ className: "dsh-github-btn-sm",
2044
+ onClick: () => loadData(true),
2045
+ style: { padding: "6px 18px", background: "var(--dsw-alias-link, #0969da)", color: "#ffffff", borderColor: "var(--dsw-alias-link, #0969da)" },
2046
+ }, "🔄 重新请求")), settingsModalEl);
2047
+ }
1237
2048
  var auth = data ? data.auth : null;
1238
2049
  var userRepos = (data && data.userRepos) || [];
1239
2050
  var workspaceMatrix = (data && data.workspaceMatrix) || [];
@@ -1329,7 +2140,7 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
1329
2140
  })));
1330
2141
  }
1331
2142
  }
1332
- return h("div", { className: "dsh-github-panel" }, headerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl);
2143
+ return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
1333
2144
  }
1334
2145
  var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
1335
2146
  function apply(ctx) {