chatccc 0.2.188 → 0.2.190

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.
Files changed (67) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +14 -14
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-config.test.ts +33 -33
  19. package/src/__tests__/card-plain-text.test.ts +5 -5
  20. package/src/__tests__/cardkit.test.ts +60 -60
  21. package/src/__tests__/cards.test.ts +77 -77
  22. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  23. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  24. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  25. package/src/__tests__/claude-adapter.test.ts +592 -592
  26. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  27. package/src/__tests__/config-reload.test.ts +4 -4
  28. package/src/__tests__/config-sample.test.ts +17 -17
  29. package/src/__tests__/feishu-api.test.ts +60 -60
  30. package/src/__tests__/feishu-platform.test.ts +22 -22
  31. package/src/__tests__/format-message.test.ts +47 -47
  32. package/src/__tests__/orchestrator.test.ts +116 -112
  33. package/src/__tests__/privacy.test.ts +198 -198
  34. package/src/__tests__/raw-stream-log.test.ts +106 -106
  35. package/src/__tests__/session.test.ts +17 -17
  36. package/src/__tests__/shared-prefix.test.ts +36 -36
  37. package/src/__tests__/stream-state.test.ts +42 -42
  38. package/src/__tests__/web-ui.test.ts +209 -130
  39. package/src/adapters/claude-adapter.ts +566 -566
  40. package/src/adapters/claude-session-meta-store.ts +120 -120
  41. package/src/adapters/codex-adapter.ts +27 -28
  42. package/src/adapters/cursor-adapter.ts +46 -46
  43. package/src/adapters/raw-stream-log.ts +124 -124
  44. package/src/adapters/resource-monitor.ts +140 -140
  45. package/src/agent-delegate-task-rpc.ts +153 -153
  46. package/src/agent-delegate-task.ts +81 -81
  47. package/src/agent-stop-stuck.ts +129 -129
  48. package/src/builtin/cli.ts +189 -189
  49. package/src/builtin/index.ts +170 -168
  50. package/src/cards.ts +137 -137
  51. package/src/chatgpt-subscription-rpc.ts +27 -27
  52. package/src/chatgpt-subscription.ts +299 -299
  53. package/src/chrome-devtools-guard.ts +318 -318
  54. package/src/codex-reset-actions.ts +184 -184
  55. package/src/config.ts +86 -86
  56. package/src/feishu-platform.ts +20 -20
  57. package/src/format-message.ts +293 -293
  58. package/src/litellm-proxy.ts +374 -374
  59. package/src/orchestrator.ts +143 -143
  60. package/src/privacy.ts +118 -118
  61. package/src/session-chat-binding.ts +6 -6
  62. package/src/session-name.ts +8 -8
  63. package/src/session.ts +98 -98
  64. package/src/shared-prefix.ts +29 -29
  65. package/src/sim-platform.ts +20 -20
  66. package/src/turn-cards.ts +117 -117
  67. package/src/web-ui.ts +142 -24
package/src/web-ui.ts CHANGED
@@ -28,7 +28,10 @@ const ILINK_AUTH_PATH = join(USER_DATA_DIR, "state", "ilink-auth.json");
28
28
 
29
29
  interface AppConfig {
30
30
  feishu?: { appId?: string; appSecret?: string };
31
- platforms?: { feishu?: { enabled?: boolean }; ilink?: { enabled?: boolean } };
31
+ platforms?: {
32
+ feishu?: { enabled?: boolean; platformType?: string };
33
+ ilink?: { enabled?: boolean; reuseTokenOnStart?: boolean };
34
+ };
32
35
  chromeDevtools?: { enabled?: boolean; port?: number; chromePath?: string };
33
36
  port?: number;
34
37
  gitTimeoutSeconds?: number;
@@ -105,6 +108,73 @@ function saveConfig(cfg: AppConfig): void {
105
108
  writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
106
109
  }
107
110
 
111
+ type ConfigApplyMode = "saved" | "reload" | "restart-required";
112
+
113
+ interface ConfigApplyResult {
114
+ mode: ConfigApplyMode;
115
+ restartRequired: boolean;
116
+ restartReasons: string[];
117
+ }
118
+
119
+ function stringValue(value: unknown): string {
120
+ return typeof value === "string" ? value.trim() : "";
121
+ }
122
+
123
+ function portValue(cfg: AppConfig): number {
124
+ return Number.isInteger(cfg.port) && cfg.port! >= 1 && cfg.port! <= 65535 ? cfg.port! : 18080;
125
+ }
126
+
127
+ function platformEnabled(cfg: AppConfig, platform: "feishu" | "ilink"): boolean {
128
+ return cfg.platforms?.[platform]?.enabled !== false;
129
+ }
130
+
131
+ function feishuPlatformType(cfg: AppConfig): string {
132
+ return cfg.platforms?.feishu?.platformType === "lark" ? "lark" : "feishu";
133
+ }
134
+
135
+ function ilinkReuseTokenOnStart(cfg: AppConfig): boolean {
136
+ return cfg.platforms?.ilink?.reuseTokenOnStart !== false;
137
+ }
138
+
139
+ export function getRestartRequiredReasons(before: AppConfig, after: AppConfig): string[] {
140
+ const reasons: string[] = [];
141
+
142
+ if (portValue(before) !== portValue(after)) reasons.push("port");
143
+ if (stringValue(before.feishu?.appId) !== stringValue(after.feishu?.appId)) reasons.push("feishu.appId");
144
+ if (stringValue(before.feishu?.appSecret) !== stringValue(after.feishu?.appSecret)) reasons.push("feishu.appSecret");
145
+ if (feishuPlatformType(before) !== feishuPlatformType(after)) reasons.push("platforms.feishu.platformType");
146
+ if (platformEnabled(before, "feishu") !== platformEnabled(after, "feishu")) reasons.push("platforms.feishu.enabled");
147
+ if (platformEnabled(before, "ilink") !== platformEnabled(after, "ilink")) reasons.push("platforms.ilink.enabled");
148
+ if (ilinkReuseTokenOnStart(before) !== ilinkReuseTokenOnStart(after)) {
149
+ reasons.push("platforms.ilink.reuseTokenOnStart");
150
+ }
151
+
152
+ return reasons;
153
+ }
154
+
155
+ async function applySavedConfigIfPossible(before: AppConfig, after: AppConfig): Promise<ConfigApplyResult> {
156
+ const setupMode = Boolean(setupActivateHook && setupHttpServer);
157
+ if (setupMode) {
158
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
159
+ }
160
+
161
+ const running = isServiceRunning();
162
+ if (!running) {
163
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
164
+ }
165
+
166
+ const restartReasons = getRestartRequiredReasons(before, after);
167
+ if (restartReasons.length > 0) {
168
+ return { mode: "restart-required", restartRequired: true, restartReasons };
169
+ }
170
+ if (!reloadConfigHook) {
171
+ return { mode: "saved", restartRequired: false, restartReasons: [] };
172
+ }
173
+
174
+ await reloadConfigHook();
175
+ return { mode: "reload", restartRequired: false, restartReasons: [] };
176
+ }
177
+
108
178
  function maskSecret(value: string | undefined): string {
109
179
  if (!value) return "(未设置)";
110
180
  if (value.length <= 8) return "***";
@@ -298,9 +368,10 @@ async function handlePostConfig(req: IncomingMessage, res: ServerResponse): Prom
298
368
  ) as AppConfig;
299
369
  try {
300
370
  saveConfig(merged);
301
- jsonReply(res, 200, { ok: true });
371
+ const applyResult = await applySavedConfigIfPossible(existing, merged);
372
+ jsonReply(res, 200, { ok: true, ...applyResult });
302
373
  } catch (err) {
303
- jsonReply(res, 500, { ok: false, error: (err as Error).message });
374
+ jsonReply(res, 500, { ok: false, saved: true, error: (err as Error).message });
304
375
  }
305
376
  }
306
377
 
@@ -451,10 +522,17 @@ async function handleStartService(_req: IncomingMessage, res: ServerResponse): P
451
522
  }
452
523
 
453
524
  if (path === "reload") {
454
- // service 已经在跑,执行真正的进程重启让所有配置(含 WSClient domain)生效
455
- jsonReply(res, 200, { ok: true, pid: process.pid, mode: "restart" });
456
- scheduleRestart();
457
- process.exit(0);
525
+ // service 已经在跑:只刷新进程内配置,让后续消息/新会话使用新值。
526
+ if (!reloadConfigHook) {
527
+ jsonReply(res, 200, { ok: true, pid: process.pid, mode: "saved" });
528
+ return;
529
+ }
530
+ try {
531
+ await reloadConfigHook();
532
+ jsonReply(res, 200, { ok: true, pid: process.pid, mode: "reload" });
533
+ } catch (err) {
534
+ jsonReply(res, 500, { ok: false, error: (err as Error).message });
535
+ }
458
536
  return;
459
537
  }
460
538
 
@@ -855,6 +933,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
855
933
  <div class="config-row"><span class="key">App ID</span><span class="val" id="cfg-APP_ID">-</span></div>
856
934
  <div class="config-row"><span class="key">App Secret</span><span class="val" id="cfg-APP_SECRET">-</span></div>
857
935
  <div class="config-row"><span class="key">平台类型</span><span class="val" id="cfg-FEISHU_PLATFORM_TYPE">-</span></div>
936
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:飞书开关、App ID、App Secret 或平台类型变更需要重启 ChatCCC。</div>
858
937
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('feishu')">编辑</button>
859
938
  </div>
860
939
  </details>
@@ -873,6 +952,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
873
952
  <button class="btn btn-outline" style="font-size:12px" onclick="forgetIlink()">忘记扫码</button>
874
953
  <span style="font-size:11px;color:#94a3b8;margin-left:8px">清除登录状态,重启后重新扫码</span>
875
954
  </div>
955
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:微信 iLink 开关变更需要重启 ChatCCC。</div>
876
956
  </div>
877
957
  </details>
878
958
 
@@ -882,7 +962,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
882
962
  <div class="config-row"><span class="key">状态</span><span class="val" id="cfg-CHROME_DEVTOOLS_ENABLED">-</span></div>
883
963
  <div class="config-row" id="cfg-CHROME_DEVTOOLS_PORT_ROW"><span class="key">CDP 端口</span><span class="val" id="cfg-CHROME_DEVTOOLS_PORT">-</span></div>
884
964
  <div class="config-row" id="cfg-CHROME_DEVTOOLS_PATH_ROW"><span class="key">Chrome 路径</span><span class="val" id="cfg-CHROME_DEVTOOLS_PATH">-</span></div>
885
- <div class="hint" style="margin-top:6px;line-height:1.6">依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
965
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后立即应用到 Chrome CDP 守护进程。依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。</div>
886
966
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('chromeDevtools')">编辑</button>
887
967
  </div>
888
968
  </details>
@@ -897,6 +977,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
897
977
  <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-ANTHROPIC_BASE_URL">-</span></div>
898
978
  <div class="config-row"><span class="key">Max Turns</span><span class="val" id="cfg-ANTHROPIC_MAX_TURN">-</span><span class="hint">(0=无限制)</span></div>
899
979
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-claude" onchange="setDashboardDefaultAgent('claude', this.checked)"> 设为默认 Agent</label>
980
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
900
981
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('claude')">编辑</button>
901
982
  </div>
902
983
  </details>
@@ -910,6 +991,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
910
991
  <div class="config-row"><span class="key">头像电池电量</span><span class="val" id="cfg-CURSOR_AVATAR_BATTERY_MODE">-</span></div>
911
992
  <div class="config-row" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET_ROW"><span class="key">每月On demand use预算</span><span class="val" id="cfg-CURSOR_ON_DEMAND_MONTHLY_BUDGET">-</span></div>
912
993
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-cursor" onchange="setDashboardDefaultAgent('cursor', this.checked)"> 设为默认 Agent</label>
994
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
913
995
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('cursor')">编辑</button>
914
996
  </div>
915
997
  </details>
@@ -922,6 +1004,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
922
1004
  <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CODEX_ALTERNATIVE_MODEL">-</span></div>
923
1005
  <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CODEX_EFFORT">-</span></div>
924
1006
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-codex" onchange="setDashboardDefaultAgent('codex', this.checked)"> 设为默认 Agent</label>
1007
+ <div class="hint" style="margin-top:6px;line-height:1.6">生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。</div>
925
1008
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('codex')">编辑</button>
926
1009
  </div>
927
1010
  </details>
@@ -938,6 +1021,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
938
1021
  <!-- Edit Modal -->
939
1022
  <div id="edit-modal" class="card hidden" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:480px;z-index:200;max-height:80vh;overflow-y:auto">
940
1023
  <h2 id="edit-modal-title">编辑配置</h2>
1024
+ <div id="edit-modal-effect" class="hint" style="margin-bottom:12px;line-height:1.6"></div>
941
1025
  <div id="edit-modal-fields"></div>
942
1026
  <div class="btn-group" style="justify-content:flex-end;margin-top:16px">
943
1027
  <button class="btn btn-outline" onclick="closeEditModal()">取消</button>
@@ -977,6 +1061,26 @@ function cursorBatteryModeLabel(value) {
977
1061
  return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
978
1062
  }
979
1063
 
1064
+ function configEffectHint(section) {
1065
+ if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
1066
+ if (section === 'chromeDevtools') return '生效范围:保存后立即应用到 Chrome CDP 守护进程。';
1067
+ if (section === 'claude') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1068
+ if (section === 'cursor') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1069
+ if (section === 'codex') return '生效范围:保存后下一条消息或下个新会话生效,当前生成不中断。';
1070
+ return '生效范围:保存后按配置类型应用。';
1071
+ }
1072
+
1073
+ function toastConfigApplyResult(result, savedText) {
1074
+ var text = savedText || '配置已保存';
1075
+ if (result && result.mode === 'reload') {
1076
+ toast(text + ',后续消息/新会话已生效。');
1077
+ } else if (result && result.mode === 'restart-required') {
1078
+ toast(text + ',需重启服务生效。');
1079
+ } else {
1080
+ toast(text);
1081
+ }
1082
+ }
1083
+
980
1084
  function onCursorBatteryModeChange(prefix, value) {
981
1085
  var rowId = prefix === 'edit-' ? 'edit-cursor-on-demand-budget-row' : 'field-cursor-on-demand-budget-row';
982
1086
  var row = document.getElementById(rowId);
@@ -1014,7 +1118,7 @@ async function onPlatformToggle(platform, enabled) {
1014
1118
  state.config.platforms[platform].enabled = enabled;
1015
1119
  var label = document.getElementById('dash-platform-' + platform + '-label');
1016
1120
  if (label) label.textContent = enabled ? '已启用' : '未启用';
1017
- toast((platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用') + '。需重启服务生效。');
1121
+ toastConfigApplyResult(result, (platform === 'feishu' ? '飞书' : '微信 iLink') + (enabled ? ' 已启用' : ' 已禁用'));
1018
1122
  } else {
1019
1123
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1020
1124
  // 还原 toggle
@@ -1420,15 +1524,16 @@ function renderStep3() {
1420
1524
  document.getElementById('review-content').innerHTML = lines.join('');
1421
1525
  }
1422
1526
 
1423
- async function saveConfig(vars) {
1527
+ async function saveConfig(vars, options) {
1528
+ options = options || {};
1424
1529
  var result = await api('/api/config', 'POST', { vars: vars });
1425
1530
  if (result.ok) {
1426
1531
  state.config = Object.assign({}, state.config, vars);
1427
- toast('配置已保存');
1532
+ if (!options.quiet) toastConfigApplyResult(result, '配置已保存');
1428
1533
  } else {
1429
1534
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1430
1535
  }
1431
- return result.ok;
1536
+ return result;
1432
1537
  }
1433
1538
 
1434
1539
  async function setDashboardDefaultAgent(agent, enabled) {
@@ -1451,7 +1556,7 @@ async function setDashboardDefaultAgent(agent, enabled) {
1451
1556
  state.config.cursor.defaultAgent = agent === 'cursor';
1452
1557
  state.config.codex.defaultAgent = agent === 'codex';
1453
1558
  updateDefaultAgentToggles();
1454
- toast('默认 Agent 已更新');
1559
+ toastConfigApplyResult(result, '默认 Agent 已更新');
1455
1560
  } else {
1456
1561
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1457
1562
  updateDefaultAgentToggles();
@@ -1464,26 +1569,36 @@ async function saveAndStart() {
1464
1569
  toast('飞书已启用,请先填写 App ID 和 App Secret', 'error');
1465
1570
  return;
1466
1571
  }
1467
- var ok = await saveConfig(vars);
1468
- if (!ok) return;
1572
+ var saved = await saveConfig(vars, { quiet: true });
1573
+ if (saved.ok !== true) return;
1469
1574
  document.getElementById('btn-save-start').disabled = true;
1470
1575
  document.getElementById('btn-save-start').innerHTML = '<span class="spinner"></span> 应用中...';
1576
+
1577
+ if (state.running && saved.restartRequired === true) {
1578
+ await api('/api/restart', 'POST');
1579
+ toast('配置已保存,服务正在重启…');
1580
+ pollUntilRunning();
1581
+ return;
1582
+ }
1583
+
1584
+ if (state.running && saved.mode === 'reload') {
1585
+ toastConfigApplyResult(saved, '配置已保存');
1586
+ setTimeout(function(){ location.reload(); }, 1000);
1587
+ return;
1588
+ }
1589
+
1471
1590
  var result = await api('/api/start', 'POST');
1472
1591
  if (result.ok) {
1473
1592
  var msg;
1474
- if (result.mode === 'restart') {
1475
- msg = '配置已保存,服务正在重启…';
1593
+ if (result.mode === 'reload') {
1594
+ msg = '配置已保存,后续消息/新会话已生效。';
1476
1595
  } else if (result.mode === 'inplace') {
1477
1596
  msg = '服务已启动! PID: ' + result.pid;
1478
1597
  } else {
1479
1598
  msg = '服务已启动! PID: ' + (result.pid || '?');
1480
1599
  }
1481
1600
  toast(msg);
1482
- if (result.mode === 'restart') {
1483
- pollUntilRunning();
1484
- } else {
1485
- setTimeout(function(){ location.reload(); }, 1500);
1486
- }
1601
+ setTimeout(function(){ location.reload(); }, 1500);
1487
1602
  } else {
1488
1603
  toast('保存失败: ' + (result.error || '未知错误'), 'error');
1489
1604
  document.getElementById('btn-save-start').disabled = false;
@@ -1666,6 +1781,8 @@ function editSection(section) {
1666
1781
  var titleMap = { feishu: '飞书', chromeDevtools: 'Chrome CDP', claude: 'Claude Agent', cursor: 'Cursor Agent', codex: 'Codex Agent' };
1667
1782
  document.getElementById('edit-modal-title').textContent = '编辑 ' + (titleMap[section] || section);
1668
1783
 
1784
+ document.getElementById('edit-modal-effect').textContent = configEffectHint(section);
1785
+
1669
1786
  var html = '';
1670
1787
  var labelMap = {
1671
1788
  'CHATCCC_APP_ID': 'App ID', 'CHATCCC_APP_SECRET': 'App Secret',
@@ -1801,14 +1918,15 @@ async function saveEdit() {
1801
1918
  var ptEl = document.getElementById('edit-CHATCCC_FEISHU_PLATFORM_TYPE');
1802
1919
  if (ptEl && ptEl.value.trim()) vars['CHATCCC_FEISHU_PLATFORM_TYPE'] = ptEl.value.trim();
1803
1920
  }
1804
- await saveConfig(vars);
1921
+ var result = await saveConfig(vars, { quiet: true });
1922
+ if (result.ok !== true) return;
1805
1923
  try {
1806
1924
  var fresh = await api('/api/config');
1807
1925
  state.config = fresh.vars || state.config;
1808
1926
  } catch(e) {}
1809
1927
  closeEditModal();
1810
1928
  updateDashboardUI();
1811
- toast('修改已保存。若服务正在运行,需重启生效。');
1929
+ toastConfigApplyResult(result, '修改已保存');
1812
1930
  }
1813
1931
 
1814
1932
  // ---- Other actions ----