@hirohsu/user-web-feedback 2.6.10 → 2.7.0

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/dist/index.cjs CHANGED
@@ -20661,6 +20661,7 @@ __export(database_exports, {
20661
20661
  getPinnedPrompts: () => getPinnedPrompts,
20662
20662
  getPromptById: () => getPromptById,
20663
20663
  getRecentMCPServerErrors: () => getRecentMCPServerErrors,
20664
+ getSelfProbeSettings: () => getSelfProbeSettings,
20664
20665
  getToolEnableConfigs: () => getToolEnableConfigs,
20665
20666
  getUserPreferences: () => getUserPreferences,
20666
20667
  initDatabase: () => initDatabase,
@@ -20676,6 +20677,7 @@ __export(database_exports, {
20676
20677
  queryLogs: () => queryLogs,
20677
20678
  queryMCPServerLogs: () => queryMCPServerLogs,
20678
20679
  reorderPrompts: () => reorderPrompts,
20680
+ saveSelfProbeSettings: () => saveSelfProbeSettings,
20679
20681
  setToolEnabled: () => setToolEnabled,
20680
20682
  toggleMCPServerEnabled: () => toggleMCPServerEnabled,
20681
20683
  togglePromptPin: () => togglePromptPin,
@@ -20974,6 +20976,14 @@ function createTables() {
20974
20976
  }
20975
20977
  } catch {
20976
20978
  }
20979
+ db.exec(`
20980
+ CREATE TABLE IF NOT EXISTS self_probe_settings (
20981
+ id INTEGER PRIMARY KEY CHECK (id = 1),
20982
+ enabled INTEGER DEFAULT 0,
20983
+ interval_seconds INTEGER DEFAULT 300,
20984
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
20985
+ )
20986
+ `);
20977
20987
  }
20978
20988
  function initDefaultSettings() {
20979
20989
  if (!db) throw new Error("Database not initialized");
@@ -22319,6 +22329,55 @@ function cleanupOldCLIExecutionLogs(daysToKeep = 7) {
22319
22329
  `).run(cutoffDate.toISOString());
22320
22330
  return result.changes;
22321
22331
  }
22332
+ function getSelfProbeSettings() {
22333
+ const db2 = tryGetDb();
22334
+ if (!db2) return void 0;
22335
+ const row = db2.prepare(`
22336
+ SELECT
22337
+ id,
22338
+ enabled,
22339
+ interval_seconds as intervalSeconds,
22340
+ updated_at as updatedAt
22341
+ FROM self_probe_settings
22342
+ WHERE id = 1
22343
+ `).get();
22344
+ if (!row) return void 0;
22345
+ return {
22346
+ id: row.id,
22347
+ enabled: row.enabled === 1,
22348
+ intervalSeconds: row.intervalSeconds,
22349
+ updatedAt: row.updatedAt
22350
+ };
22351
+ }
22352
+ function saveSelfProbeSettings(settings) {
22353
+ const db2 = tryGetDb();
22354
+ if (!db2) throw new Error("Database not initialized");
22355
+ const existing = getSelfProbeSettings();
22356
+ const now = (/* @__PURE__ */ new Date()).toISOString();
22357
+ if (existing) {
22358
+ db2.prepare(`
22359
+ UPDATE self_probe_settings
22360
+ SET enabled = COALESCE(?, enabled),
22361
+ interval_seconds = COALESCE(?, interval_seconds),
22362
+ updated_at = ?
22363
+ WHERE id = 1
22364
+ `).run(
22365
+ settings.enabled !== void 0 ? settings.enabled ? 1 : 0 : null,
22366
+ settings.intervalSeconds ?? null,
22367
+ now
22368
+ );
22369
+ } else {
22370
+ db2.prepare(`
22371
+ INSERT INTO self_probe_settings (id, enabled, interval_seconds, updated_at)
22372
+ VALUES (1, ?, ?, ?)
22373
+ `).run(
22374
+ settings.enabled ? 1 : 0,
22375
+ settings.intervalSeconds ?? 300,
22376
+ now
22377
+ );
22378
+ }
22379
+ return getSelfProbeSettings();
22380
+ }
22322
22381
  var import_better_sqlite3, import_path2, import_fs2, import_crypto2, DB_DIR, DB_PATH, db, SYSTEM_PROMPT_VERSIONS, CURRENT_PROMPT_VERSION;
22323
22382
  var init_database = __esm({
22324
22383
  "src/utils/database.ts"() {
@@ -89361,6 +89420,127 @@ var InstanceLock = class {
89361
89420
 
89362
89421
  // src/server/web-server.ts
89363
89422
  init_database();
89423
+
89424
+ // src/utils/self-probe-service.ts
89425
+ init_cjs_shims();
89426
+ init_logger();
89427
+ init_database();
89428
+ var SelfProbeService = class {
89429
+ constructor(config2) {
89430
+ this.config = config2;
89431
+ this.enabled = config2.enableSelfProbe ?? false;
89432
+ this.intervalSeconds = config2.selfProbeIntervalSeconds ?? 300;
89433
+ }
89434
+ timer = null;
89435
+ lastProbeTime = null;
89436
+ probeCount = 0;
89437
+ enabled = false;
89438
+ intervalSeconds = 300;
89439
+ context = null;
89440
+ setContext(context) {
89441
+ this.context = context;
89442
+ }
89443
+ start() {
89444
+ if (this.timer) {
89445
+ this.stop();
89446
+ }
89447
+ const dbSettings = getSelfProbeSettings();
89448
+ if (dbSettings) {
89449
+ this.enabled = dbSettings.enabled;
89450
+ this.intervalSeconds = dbSettings.intervalSeconds;
89451
+ }
89452
+ if (!this.enabled) {
89453
+ logger.debug("Self-probe is disabled, not starting");
89454
+ return;
89455
+ }
89456
+ const intervalMs = this.intervalSeconds * 1e3;
89457
+ this.timer = setInterval(() => this.probe(), intervalMs);
89458
+ logger.info(`Self-probe started with interval: ${this.intervalSeconds}s`);
89459
+ }
89460
+ stop() {
89461
+ if (this.timer) {
89462
+ clearInterval(this.timer);
89463
+ this.timer = null;
89464
+ logger.info("Self-probe stopped");
89465
+ }
89466
+ }
89467
+ restart() {
89468
+ this.stop();
89469
+ this.start();
89470
+ }
89471
+ updateSettings(settings) {
89472
+ if (settings.enabled !== void 0) {
89473
+ this.enabled = settings.enabled;
89474
+ }
89475
+ if (settings.intervalSeconds !== void 0) {
89476
+ if (settings.intervalSeconds < 60 || settings.intervalSeconds > 600) {
89477
+ throw new Error("Interval must be between 60 and 600 seconds");
89478
+ }
89479
+ this.intervalSeconds = settings.intervalSeconds;
89480
+ }
89481
+ saveSelfProbeSettings(settings);
89482
+ if (this.enabled) {
89483
+ this.restart();
89484
+ } else {
89485
+ this.stop();
89486
+ }
89487
+ }
89488
+ async probe() {
89489
+ this.lastProbeTime = /* @__PURE__ */ new Date();
89490
+ this.probeCount++;
89491
+ try {
89492
+ if (this.context) {
89493
+ this.checkSocketIO();
89494
+ this.checkMCPStatus();
89495
+ this.triggerSessionCleanup();
89496
+ }
89497
+ logger.debug(`Self-probe #${this.probeCount} completed`);
89498
+ } catch (error2) {
89499
+ logger.warn("Self-probe encountered an issue:", error2);
89500
+ }
89501
+ }
89502
+ checkSocketIO() {
89503
+ if (!this.context) return;
89504
+ const connectedSockets = this.context.getSocketIOConnectionCount();
89505
+ logger.debug(`Self-probe: Socket.IO connected clients: ${connectedSockets}`);
89506
+ }
89507
+ checkMCPStatus() {
89508
+ if (!this.context) return;
89509
+ const mcpStatus = this.context.getMCPServerStatus();
89510
+ logger.debug(`Self-probe: MCP Server running: ${mcpStatus?.running ?? "N/A"}`);
89511
+ }
89512
+ triggerSessionCleanup() {
89513
+ if (!this.context) return;
89514
+ const sessionCount = this.context.getSessionCount();
89515
+ if (sessionCount > 0) {
89516
+ this.context.cleanupExpiredSessions();
89517
+ logger.debug(`Self-probe: Triggered session cleanup, active sessions: ${sessionCount}`);
89518
+ }
89519
+ }
89520
+ getStats() {
89521
+ return {
89522
+ enabled: this.enabled,
89523
+ intervalSeconds: this.intervalSeconds,
89524
+ lastProbeTime: this.lastProbeTime,
89525
+ probeCount: this.probeCount,
89526
+ isRunning: this.timer !== null
89527
+ };
89528
+ }
89529
+ isEnabled() {
89530
+ return this.enabled;
89531
+ }
89532
+ isRunning() {
89533
+ return this.timer !== null;
89534
+ }
89535
+ getProbeCount() {
89536
+ return this.probeCount;
89537
+ }
89538
+ getLastProbeTime() {
89539
+ return this.lastProbeTime;
89540
+ }
89541
+ };
89542
+
89543
+ // src/server/web-server.ts
89364
89544
  init_crypto_helper();
89365
89545
  init_ai_service();
89366
89546
  init_mcp_client_manager();
@@ -89536,6 +89716,7 @@ var WebServer = class {
89536
89716
  sseTransports = /* @__PURE__ */ new Map();
89537
89717
  sseTransportsList = [];
89538
89718
  dbInitialized = false;
89719
+ selfProbeService;
89539
89720
  /**
89540
89721
  * 延遲載入 ImageProcessor
89541
89722
  */
@@ -89577,6 +89758,7 @@ var WebServer = class {
89577
89758
  this.config = config2;
89578
89759
  this.portManager = new PortManager();
89579
89760
  this.sessionStorage = new SessionStorage();
89761
+ this.selfProbeService = new SelfProbeService(config2);
89580
89762
  this.app = (0, import_express.default)();
89581
89763
  this.server = (0, import_http.createServer)(this.app);
89582
89764
  this.io = new Server2(this.server, {
@@ -89841,6 +90023,7 @@ var WebServer = class {
89841
90023
  });
89842
90024
  });
89843
90025
  this.app.get("/api/health", (req, res) => {
90026
+ const selfProbeStats = this.selfProbeService.getStats();
89844
90027
  res.json({
89845
90028
  status: "ok",
89846
90029
  pid: process.pid,
@@ -89848,7 +90031,14 @@ var WebServer = class {
89848
90031
  uptime: process.uptime(),
89849
90032
  version: VERSION,
89850
90033
  activeSessions: this.sessionStorage.getSessionCount(),
89851
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
90034
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
90035
+ selfProbe: {
90036
+ enabled: selfProbeStats.enabled,
90037
+ isRunning: selfProbeStats.isRunning,
90038
+ probeCount: selfProbeStats.probeCount,
90039
+ lastProbeTime: selfProbeStats.lastProbeTime?.toISOString() ?? null,
90040
+ intervalSeconds: selfProbeStats.intervalSeconds
90041
+ }
89852
90042
  });
89853
90043
  });
89854
90044
  this.app.get("/api/dashboard/overview", (req, res) => {
@@ -90376,6 +90566,46 @@ var WebServer = class {
90376
90566
  });
90377
90567
  }
90378
90568
  });
90569
+ this.app.get("/api/settings/self-probe", (req, res) => {
90570
+ try {
90571
+ const settings = getSelfProbeSettings();
90572
+ const stats = this.selfProbeService.getStats();
90573
+ res.json({
90574
+ success: true,
90575
+ settings: settings ?? { enabled: false, intervalSeconds: 300 },
90576
+ stats
90577
+ });
90578
+ } catch (error2) {
90579
+ logger.error("\u7372\u53D6 Self-Probe \u8A2D\u5B9A\u5931\u6557:", error2);
90580
+ res.status(500).json({
90581
+ success: false,
90582
+ error: error2 instanceof Error ? error2.message : "\u7372\u53D6 Self-Probe \u8A2D\u5B9A\u5931\u6557"
90583
+ });
90584
+ }
90585
+ });
90586
+ this.app.post("/api/settings/self-probe", (req, res) => {
90587
+ try {
90588
+ const { enabled, intervalSeconds } = req.body;
90589
+ if (intervalSeconds !== void 0 && (intervalSeconds < 60 || intervalSeconds > 600)) {
90590
+ res.status(400).json({
90591
+ success: false,
90592
+ error: "Interval must be between 60 and 600 seconds"
90593
+ });
90594
+ return;
90595
+ }
90596
+ this.selfProbeService.updateSettings({ enabled, intervalSeconds });
90597
+ const settings = getSelfProbeSettings();
90598
+ const stats = this.selfProbeService.getStats();
90599
+ logger.info(`Self-Probe \u8A2D\u5B9A\u5DF2\u66F4\u65B0: enabled=${enabled}, interval=${intervalSeconds}s`);
90600
+ res.json({ success: true, settings, stats });
90601
+ } catch (error2) {
90602
+ logger.error("\u66F4\u65B0 Self-Probe \u8A2D\u5B9A\u5931\u6557:", error2);
90603
+ res.status(500).json({
90604
+ success: false,
90605
+ error: error2 instanceof Error ? error2.message : "\u66F4\u65B0 Self-Probe \u8A2D\u5B9A\u5931\u6557"
90606
+ });
90607
+ }
90608
+ });
90379
90609
  this.app.get("/api/logs", (req, res) => {
90380
90610
  try {
90381
90611
  const options = {};
@@ -91733,6 +91963,13 @@ var WebServer = class {
91733
91963
  }
91734
91964
  logger.mcpServerStarted(this.port, serverUrl);
91735
91965
  await this.autoStartMCPServers();
91966
+ this.selfProbeService.setContext({
91967
+ getSocketIOConnectionCount: () => this.io.sockets.sockets.size,
91968
+ getMCPServerStatus: () => this.mcpServerRef?.getStatus(),
91969
+ getSessionCount: () => this.sessionStorage.getSessionCount(),
91970
+ cleanupExpiredSessions: () => this.sessionStorage.cleanupExpiredSessions()
91971
+ });
91972
+ this.selfProbeService.start();
91736
91973
  } catch (error2) {
91737
91974
  logger.error("Web\u4F3A\u670D\u5668\u555F\u52D5\u5931\u6557:", error2);
91738
91975
  throw new MCPError(
@@ -91909,6 +92146,7 @@ var WebServer = class {
91909
92146
  logger.warn("\u7B49\u5F85\u6D3B\u8E8D\u6703\u8A71\u5B8C\u6210\u6642\u767C\u751F\u932F\u8AA4\u6216\u8D85\u6642\uFF0C\u5C07\u7E7C\u7E8C\u505C\u6B62\u6D41\u7A0B", waitErr);
91910
92147
  }
91911
92148
  }
92149
+ this.selfProbeService.stop();
91912
92150
  this.sessionStorage.clear();
91913
92151
  this.sessionStorage.stopCleanupTimer();
91914
92152
  this.io.disconnectSockets(true);
@@ -91952,6 +92190,24 @@ var WebServer = class {
91952
92190
  getPort() {
91953
92191
  return this.port;
91954
92192
  }
92193
+ /**
92194
+ * 取得 Self-Probe 服務實例
92195
+ */
92196
+ getSelfProbeService() {
92197
+ return this.selfProbeService;
92198
+ }
92199
+ /**
92200
+ * 取得 Socket.IO 伺服器實例
92201
+ */
92202
+ getIO() {
92203
+ return this.io;
92204
+ }
92205
+ /**
92206
+ * 取得 Session Storage 實例
92207
+ */
92208
+ getSessionStorage() {
92209
+ return this.sessionStorage;
92210
+ }
91955
92211
  };
91956
92212
 
91957
92213
  // src/server/mcp-server.ts
@@ -92393,6 +92649,26 @@ var MCPServer = class {
92393
92649
  // src/config/index.ts
92394
92650
  init_cjs_shims();
92395
92651
  var import_dotenv = __toESM(require_main2(), 1);
92652
+
92653
+ // src/shared/ipc-constants.ts
92654
+ init_cjs_shims();
92655
+ var SUPERVISOR_DEFAULTS = {
92656
+ MAX_RESTART_ATTEMPTS: 5,
92657
+ RESTART_DELAY_MS: 2e3,
92658
+ HEALTH_CHECK_INTERVAL_MS: 3e4,
92659
+ HEALTH_CHECK_TIMEOUT_MS: 5e3,
92660
+ REQUEST_TIMEOUT_MS: 3e4,
92661
+ CONSECUTIVE_FAILURES_BEFORE_RESTART: 3
92662
+ };
92663
+ var SUPERVISOR_ENV_KEYS = {
92664
+ ENABLED: "SUPERVISOR_ENABLED",
92665
+ MAX_RESTART_ATTEMPTS: "SUPERVISOR_MAX_RESTART_ATTEMPTS",
92666
+ RESTART_DELAY_MS: "SUPERVISOR_RESTART_DELAY_MS",
92667
+ HEALTH_CHECK_INTERVAL_MS: "SUPERVISOR_HEALTH_CHECK_INTERVAL_MS",
92668
+ HEALTH_CHECK_TIMEOUT_MS: "SUPERVISOR_HEALTH_CHECK_TIMEOUT_MS"
92669
+ };
92670
+
92671
+ // src/config/index.ts
92396
92672
  (0, import_dotenv.config)();
92397
92673
  function getEnvVar(key, defaultValue) {
92398
92674
  return process.env[key] || defaultValue;
@@ -92415,6 +92691,27 @@ function getEnvBoolean(key, defaultValue) {
92415
92691
  if (!value) return defaultValue;
92416
92692
  return value.toLowerCase() === "true";
92417
92693
  }
92694
+ function createSupervisorConfig() {
92695
+ return {
92696
+ enabled: getEnvBoolean(SUPERVISOR_ENV_KEYS.ENABLED, false),
92697
+ maxRestartAttempts: getEnvNumber(
92698
+ SUPERVISOR_ENV_KEYS.MAX_RESTART_ATTEMPTS,
92699
+ SUPERVISOR_DEFAULTS.MAX_RESTART_ATTEMPTS
92700
+ ),
92701
+ restartDelayMs: getEnvNumber(
92702
+ SUPERVISOR_ENV_KEYS.RESTART_DELAY_MS,
92703
+ SUPERVISOR_DEFAULTS.RESTART_DELAY_MS
92704
+ ),
92705
+ healthCheckIntervalMs: getEnvNumber(
92706
+ SUPERVISOR_ENV_KEYS.HEALTH_CHECK_INTERVAL_MS,
92707
+ SUPERVISOR_DEFAULTS.HEALTH_CHECK_INTERVAL_MS
92708
+ ),
92709
+ healthCheckTimeoutMs: getEnvNumber(
92710
+ SUPERVISOR_ENV_KEYS.HEALTH_CHECK_TIMEOUT_MS,
92711
+ SUPERVISOR_DEFAULTS.HEALTH_CHECK_TIMEOUT_MS
92712
+ )
92713
+ };
92714
+ }
92418
92715
  function createDefaultConfig() {
92419
92716
  return {
92420
92717
  apiKey: process.env["MCP_API_KEY"],
@@ -92446,7 +92743,12 @@ function createDefaultConfig() {
92446
92743
  healthCheckTimeout: getEnvNumber("MCP_HEALTH_CHECK_TIMEOUT", 3e3),
92447
92744
  forceNewInstance: getEnvBoolean("MCP_FORCE_NEW_INSTANCE", false),
92448
92745
  // MCP Server 傳輸模式
92449
- mcpTransport: getEnvVar("MCP_TRANSPORT", "stdio")
92746
+ mcpTransport: getEnvVar("MCP_TRANSPORT", "stdio"),
92747
+ // Self-Probe (Keep-Alive) 設定
92748
+ enableSelfProbe: getEnvBoolean("MCP_ENABLE_SELF_PROBE", false),
92749
+ selfProbeIntervalSeconds: getEnvNumber("MCP_SELF_PROBE_INTERVAL", 300),
92750
+ // Supervisor 配置
92751
+ supervisor: createSupervisorConfig()
92450
92752
  };
92451
92753
  }
92452
92754
  function validateConfig(config2) {
@@ -92483,6 +92785,41 @@ function validateConfig(config2) {
92483
92785
  "INVALID_LOG_LEVEL"
92484
92786
  );
92485
92787
  }
92788
+ if (config2.selfProbeIntervalSeconds !== void 0) {
92789
+ if (config2.selfProbeIntervalSeconds < 60 || config2.selfProbeIntervalSeconds > 600) {
92790
+ throw new MCPError(
92791
+ `Invalid self-probe interval: ${config2.selfProbeIntervalSeconds}. Must be between 60 and 600 seconds.`,
92792
+ "INVALID_SELF_PROBE_INTERVAL"
92793
+ );
92794
+ }
92795
+ }
92796
+ if (config2.supervisor) {
92797
+ const sup = config2.supervisor;
92798
+ if (sup.maxRestartAttempts < 0 || sup.maxRestartAttempts > 20) {
92799
+ throw new MCPError(
92800
+ `Invalid supervisor max restart attempts: ${sup.maxRestartAttempts}. Must be between 0 and 20.`,
92801
+ "INVALID_SUPERVISOR_CONFIG"
92802
+ );
92803
+ }
92804
+ if (sup.restartDelayMs < 100 || sup.restartDelayMs > 3e4) {
92805
+ throw new MCPError(
92806
+ `Invalid supervisor restart delay: ${sup.restartDelayMs}. Must be between 100ms and 30000ms.`,
92807
+ "INVALID_SUPERVISOR_CONFIG"
92808
+ );
92809
+ }
92810
+ if (sup.healthCheckIntervalMs < 5e3 || sup.healthCheckIntervalMs > 3e5) {
92811
+ throw new MCPError(
92812
+ `Invalid supervisor health check interval: ${sup.healthCheckIntervalMs}. Must be between 5s and 300s.`,
92813
+ "INVALID_SUPERVISOR_CONFIG"
92814
+ );
92815
+ }
92816
+ if (sup.healthCheckTimeoutMs < 1e3 || sup.healthCheckTimeoutMs > 3e4) {
92817
+ throw new MCPError(
92818
+ `Invalid supervisor health check timeout: ${sup.healthCheckTimeoutMs}. Must be between 1s and 30s.`,
92819
+ "INVALID_SUPERVISOR_CONFIG"
92820
+ );
92821
+ }
92822
+ }
92486
92823
  }
92487
92824
  function getConfig() {
92488
92825
  const config2 = createDefaultConfig();
@@ -342,6 +342,42 @@
342
342
  <button id="savePreferencesBtn" class="btn btn-primary">儲存偏好設定</button>
343
343
  </div>
344
344
  </section>
345
+
346
+ <!-- Self-Probe 設定 -->
347
+ <section class="settings-section">
348
+ <h2 class="section-title">
349
+ <span class="icon">💓</span>
350
+ 自我探查 (Keep-Alive)
351
+ </h2>
352
+
353
+ <div class="form-group">
354
+ <div class="checkbox-group">
355
+ <input type="checkbox" id="enableSelfProbe" />
356
+ <label for="enableSelfProbe">啟用自我探查</label>
357
+ </div>
358
+ <p class="form-help">定期檢查服務狀態,防止因閒置被系統回收</p>
359
+ </div>
360
+
361
+ <div class="form-group" id="selfProbeIntervalGroup">
362
+ <label class="form-label" for="selfProbeInterval">探查間隔(秒)</label>
363
+ <input type="number" id="selfProbeInterval" class="form-input"
364
+ min="60" max="600" step="30" value="300">
365
+ <p class="form-help">60-600 秒,預設 300 秒(5 分鐘)</p>
366
+ </div>
367
+
368
+ <div class="form-group" id="selfProbeStatus" style="display: none;">
369
+ <label class="form-label">狀態資訊</label>
370
+ <div class="status-info" style="font-size: 13px; color: var(--text-muted); padding: 8px; background: var(--bg-secondary); border-radius: var(--radius-sm);">
371
+ <div id="selfProbeRunning">執行狀態: --</div>
372
+ <div id="selfProbeCount">探查次數: --</div>
373
+ <div id="selfProbeLastTime">上次探查: --</div>
374
+ </div>
375
+ </div>
376
+
377
+ <div class="form-actions">
378
+ <button id="saveSelfProbeBtn" class="btn btn-primary">儲存設定</button>
379
+ </div>
380
+ </section>
345
381
  </div>
346
382
 
347
383
  <!-- Toast Container -->
@@ -63,6 +63,15 @@
63
63
  confirmBeforeSubmit: document.getElementById("confirmBeforeSubmit"),
64
64
  defaultLanguage: document.getElementById("defaultLanguage"),
65
65
  savePreferencesBtn: document.getElementById("savePreferencesBtn"),
66
+ // Self-Probe Settings
67
+ enableSelfProbe: document.getElementById("enableSelfProbe"),
68
+ selfProbeInterval: document.getElementById("selfProbeInterval"),
69
+ selfProbeIntervalGroup: document.getElementById("selfProbeIntervalGroup"),
70
+ selfProbeStatus: document.getElementById("selfProbeStatus"),
71
+ selfProbeRunning: document.getElementById("selfProbeRunning"),
72
+ selfProbeCount: document.getElementById("selfProbeCount"),
73
+ selfProbeLastTime: document.getElementById("selfProbeLastTime"),
74
+ saveSelfProbeBtn: document.getElementById("saveSelfProbeBtn"),
66
75
  toastContainer: document.getElementById("toastContainer"),
67
76
  };
68
77
 
@@ -76,6 +85,7 @@
76
85
  loadAISettings();
77
86
  loadCLISettings();
78
87
  loadPreferences();
88
+ loadSelfProbeSettings();
79
89
  }
80
90
 
81
91
  function setupEventListeners() {
@@ -92,6 +102,21 @@
92
102
 
93
103
  // User Preferences
94
104
  elements.savePreferencesBtn.addEventListener("click", savePreferences);
105
+
106
+ // Self-Probe Settings
107
+ if (elements.enableSelfProbe) {
108
+ elements.enableSelfProbe.addEventListener("change", handleSelfProbeToggle);
109
+ }
110
+ if (elements.saveSelfProbeBtn) {
111
+ elements.saveSelfProbeBtn.addEventListener("click", saveSelfProbeSettings);
112
+ }
113
+ }
114
+
115
+ function handleSelfProbeToggle() {
116
+ const isEnabled = elements.enableSelfProbe.checked;
117
+ if (elements.selfProbeIntervalGroup) {
118
+ elements.selfProbeIntervalGroup.style.opacity = isEnabled ? "1" : "0.5";
119
+ }
95
120
  }
96
121
 
97
122
  function handleAIModeChange() {
@@ -397,6 +422,99 @@
397
422
  }
398
423
  }
399
424
 
425
+ // ============ Self-Probe Settings ============
426
+
427
+ async function loadSelfProbeSettings() {
428
+ try {
429
+ const response = await fetch(`${API_BASE}/api/settings/self-probe`);
430
+ const data = await response.json();
431
+
432
+ if (data.success) {
433
+ const settings = data.settings || {};
434
+ const stats = data.stats || {};
435
+
436
+ if (elements.enableSelfProbe) {
437
+ elements.enableSelfProbe.checked = settings.enabled || false;
438
+ }
439
+ if (elements.selfProbeInterval) {
440
+ elements.selfProbeInterval.value = settings.intervalSeconds || 300;
441
+ }
442
+
443
+ // 更新狀態資訊
444
+ updateSelfProbeStatus(stats);
445
+ handleSelfProbeToggle();
446
+ }
447
+ } catch (error) {
448
+ console.error("Failed to load Self-Probe settings:", error);
449
+ }
450
+ }
451
+
452
+ function updateSelfProbeStatus(stats) {
453
+ if (!elements.selfProbeStatus) return;
454
+
455
+ if (stats.enabled) {
456
+ elements.selfProbeStatus.style.display = "block";
457
+
458
+ if (elements.selfProbeRunning) {
459
+ elements.selfProbeRunning.textContent = `執行狀態: ${stats.isRunning ? "✅ 運行中" : "⏸️ 已停止"}`;
460
+ }
461
+ if (elements.selfProbeCount) {
462
+ elements.selfProbeCount.textContent = `探查次數: ${stats.probeCount || 0}`;
463
+ }
464
+ if (elements.selfProbeLastTime) {
465
+ const lastTime = stats.lastProbeTime
466
+ ? new Date(stats.lastProbeTime).toLocaleString()
467
+ : "尚未執行";
468
+ elements.selfProbeLastTime.textContent = `上次探查: ${lastTime}`;
469
+ }
470
+ } else {
471
+ elements.selfProbeStatus.style.display = "none";
472
+ }
473
+ }
474
+
475
+ async function saveSelfProbeSettings() {
476
+ const settings = {
477
+ enabled: elements.enableSelfProbe?.checked || false,
478
+ intervalSeconds: parseInt(elements.selfProbeInterval?.value) || 300,
479
+ };
480
+
481
+ // 驗證間隔
482
+ if (settings.intervalSeconds < 60 || settings.intervalSeconds > 600) {
483
+ showToast("探查間隔必須在 60-600 秒之間", "error");
484
+ return;
485
+ }
486
+
487
+ if (elements.saveSelfProbeBtn) {
488
+ elements.saveSelfProbeBtn.disabled = true;
489
+ elements.saveSelfProbeBtn.textContent = "儲存中...";
490
+ }
491
+
492
+ try {
493
+ const response = await fetch(`${API_BASE}/api/settings/self-probe`, {
494
+ method: "POST",
495
+ headers: { "Content-Type": "application/json" },
496
+ body: JSON.stringify(settings),
497
+ });
498
+
499
+ const data = await response.json();
500
+
501
+ if (response.ok && data.success) {
502
+ showToast("Self-Probe 設定已儲存", "success");
503
+ updateSelfProbeStatus(data.stats);
504
+ } else {
505
+ showToast(`儲存失敗: ${data.error || "未知錯誤"}`, "error");
506
+ }
507
+ } catch (error) {
508
+ console.error("Save Self-Probe settings failed:", error);
509
+ showToast("儲存失敗", "error");
510
+ } finally {
511
+ if (elements.saveSelfProbeBtn) {
512
+ elements.saveSelfProbeBtn.disabled = false;
513
+ elements.saveSelfProbeBtn.textContent = "儲存設定";
514
+ }
515
+ }
516
+ }
517
+
400
518
  function showToast(message, type = "info") {
401
519
  const toast = document.createElement("div");
402
520
  toast.className = `toast toast-${type}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirohsu/user-web-feedback",
3
- "version": "2.6.10",
3
+ "version": "2.7.0",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {