diff --git a/CHANGELOG.md b/CHANGELOG.md
index c81a682..b5bf088 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# 更新记录
+## v1.2.9
+
+### 新功能
+
+- **通知 AI 摘要** — 任务完成时调用 Claude API 生成摘要内容推送,支持正常完成/异常/上下文压缩等多种情况分类,摘要 API 凭证可独立配置或复用活跃 Claude 模板/Codex Profile,各渠道按字符限制自动截断,摘要失败时降级为原始信息
+- **通知配置收进二级菜单** — Claude 和 Codex 设置面板中的通知区域改为 nav-card 入口,点击进入独立子页,与主题设置风格统一
+
## v1.2.8
### 新功能
diff --git a/public/app.js b/public/app.js
index 7a56d3b..2ce7369 100644
--- a/public/app.js
+++ b/public/app.js
@@ -222,6 +222,134 @@
`;
}
+ function buildNotifyEntryHtml(config) {
+ const provider = config?.provider || 'off';
+ const providerLabel = PROVIDER_OPTIONS.find(o => o.value === provider)?.label || '关闭';
+ const summaryOn = config?.summary?.enabled ? '摘要已启用' : '摘要关闭';
+ const meta = provider === 'off' ? '未启用' : `${providerLabel} · ${summaryOn}`;
+ return `
+
通知
+
+ `;
+ }
+
+ function openNotifySubpage() {
+ send({ type: 'get_notify_config' });
+
+ const overlay = document.createElement('div');
+ overlay.className = 'settings-overlay settings-subpage-overlay';
+ overlay.style.zIndex = '10001';
+
+ const panel = document.createElement('div');
+ panel.className = 'settings-panel settings-subpage-panel';
+ panel.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ overlay.appendChild(panel);
+ document.body.appendChild(overlay);
+
+ const providerSelect = panel.querySelector('#notify-provider');
+ const fieldsDiv = panel.querySelector('#notify-fields');
+ const summaryArea = panel.querySelector('#notify-summary-area');
+ const statusDiv = panel.querySelector('#notify-status');
+ const testBtn = panel.querySelector('#notify-test-btn');
+ const saveBtn = panel.querySelector('#notify-save-btn');
+
+ let currentNotifyConfig = null;
+
+ function renderFields(provider) {
+ renderNotifyFields(fieldsDiv, currentNotifyConfig, provider);
+ if (summaryArea) {
+ summaryArea.innerHTML = buildSummarySettingsHtml(currentNotifyConfig);
+ bindSummarySettingsEvents(panel);
+ }
+ }
+
+ function collectConfig() {
+ return collectNotifyConfigFromPanel(panel, currentNotifyConfig, providerSelect.value);
+ }
+
+ function showStatus(msg, type) {
+ statusDiv.textContent = msg;
+ statusDiv.className = 'settings-status ' + (type || '');
+ }
+
+ function refreshParentSummary(config) {
+ const provider = config?.provider || 'off';
+ const providerLabel = PROVIDER_OPTIONS.find(o => o.value === provider)?.label || '关闭';
+ const summaryOn = config?.summary?.enabled ? '摘要已启用' : '摘要关闭';
+ const meta = provider === 'off' ? '未启用' : `${providerLabel} · ${summaryOn}`;
+ document.querySelectorAll('[data-notify-summary]').forEach(el => { el.textContent = meta; });
+ }
+
+ const savedOnNotifyConfig = _onNotifyConfig;
+ _onNotifyConfig = (config) => {
+ currentNotifyConfig = config;
+ providerSelect.value = config.provider || 'off';
+ renderFields(config.provider || 'off');
+ if (savedOnNotifyConfig) savedOnNotifyConfig(config);
+ };
+
+ const savedOnNotifyTestResult = _onNotifyTestResult;
+ _onNotifyTestResult = (msg) => {
+ showStatus(msg.message, msg.success ? 'success' : 'error');
+ if (savedOnNotifyTestResult) savedOnNotifyTestResult(msg);
+ };
+
+ providerSelect.addEventListener('change', () => renderFields(providerSelect.value));
+
+ testBtn.addEventListener('click', () => {
+ const config = collectConfig();
+ send({ type: 'save_notify_config', config });
+ showStatus('正在发送测试消息...', '');
+ send({ type: 'test_notify' });
+ });
+
+ saveBtn.addEventListener('click', () => {
+ const config = collectConfig();
+ send({ type: 'save_notify_config', config });
+ refreshParentSummary(config);
+ showStatus('已保存', 'success');
+ });
+
+ const closeSubpage = () => {
+ _onNotifyConfig = savedOnNotifyConfig;
+ _onNotifyTestResult = savedOnNotifyTestResult;
+ if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
+ };
+
+ panel.querySelector('.settings-back').addEventListener('click', closeSubpage);
+ panel.querySelector('.settings-close').addEventListener('click', closeSubpage);
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSubpage(); });
+ }
+
function openThemeSubpage() {
const overlay = document.createElement('div');
overlay.className = 'settings-overlay settings-subpage-overlay';
@@ -3025,7 +3153,6 @@
}
function showCodexSettingsPanel() {
- send({ type: 'get_notify_config' });
send({ type: 'get_codex_config' });
const overlay = document.createElement('div');
@@ -3060,20 +3187,7 @@
- 通知设置
-
-
-
-
-
-
-
-
-
-
-
+ ${buildNotifyEntryHtml(null)}
@@ -3089,6 +3203,8 @@
document.body.appendChild(overlay);
const themePageBtn = panel.querySelector('[data-open-theme-page]');
if (themePageBtn) themePageBtn.addEventListener('click', openThemeSubpage);
+ const notifyPageBtn = panel.querySelector('[data-open-notify-page]');
+ if (notifyPageBtn) notifyPageBtn.addEventListener('click', openNotifySubpage);
const closeBtn = panel.querySelector('.settings-close');
const codexModeSelect = panel.querySelector('#codex-mode');
@@ -3096,17 +3212,10 @@
const codexStatus = panel.querySelector('#codex-status');
const codexSaveBtn = panel.querySelector('#codex-save-btn');
- const providerSelect = panel.querySelector('#notify-provider');
- const fieldsDiv = panel.querySelector('#notify-fields');
- const summaryArea = panel.querySelector('#notify-summary-area');
- const statusDiv = panel.querySelector('#notify-status');
- const testBtn = panel.querySelector('#notify-test-btn');
- const saveBtn = panel.querySelector('#notify-save-btn');
const pwOpenModalBtn = panel.querySelector('#pw-open-modal-btn');
const checkUpdateBtn = panel.querySelector('#check-update-btn');
const updateStatusEl = panel.querySelector('#update-status');
- let currentNotifyConfig = null;
let currentCodexConfig = null;
let codexEditingProfiles = [];
let codexActiveProfile = '';
@@ -3117,23 +3226,6 @@
codexStatus.className = 'settings-status ' + (type || '');
}
- function renderFields(provider) {
- renderNotifyFields(fieldsDiv, currentNotifyConfig, provider);
- if (summaryArea) {
- summaryArea.innerHTML = buildSummarySettingsHtml(currentNotifyConfig);
- bindSummarySettingsEvents(panel);
- }
- }
-
- function collectNotifyConfig() {
- return collectNotifyConfigFromPanel(panel, currentNotifyConfig, providerSelect.value);
- }
-
- function showNotifyStatus(msg, type) {
- statusDiv.textContent = msg;
- statusDiv.className = 'settings-status ' + (type || '');
- }
-
function renderCodexProfileArea() {
const mode = codexModeSelect.value;
if (mode === 'local') {
@@ -3293,17 +3385,6 @@
renderCodexProfileArea();
};
- _onNotifyConfig = (config) => {
- currentNotifyConfig = config;
- providerSelect.value = config.provider || 'off';
- renderFields(config.provider || 'off');
- };
-
- _onNotifyTestResult = (msg) => {
- showNotifyStatus(msg.message, msg.success ? 'success' : 'error');
- };
-
- providerSelect.addEventListener('change', () => renderFields(providerSelect.value));
codexModeSelect.addEventListener('change', renderCodexProfileArea);
codexSaveBtn.addEventListener('click', () => {
@@ -3321,19 +3402,6 @@
showCodexStatus('已保存', 'success');
});
- testBtn.addEventListener('click', () => {
- const config = collectNotifyConfig();
- send({ type: 'save_notify_config', config });
- showNotifyStatus('正在发送测试消息...', '');
- send({ type: 'test_notify' });
- });
-
- saveBtn.addEventListener('click', () => {
- const config = collectNotifyConfig();
- send({ type: 'save_notify_config', config });
- showNotifyStatus('已保存', 'success');
- });
-
pwOpenModalBtn.addEventListener('click', openPasswordModal);
checkUpdateBtn.addEventListener('click', () => {
@@ -3369,8 +3437,7 @@
showCodexSettingsPanel();
return;
}
- // Request current configs
- send({ type: 'get_notify_config' });
+ // Request current configs (notify config is loaded on demand inside subpage)
send({ type: 'get_model_config' });
const overlay = document.createElement('div');
@@ -3406,20 +3473,7 @@
- 通知设置
-
-
-
-
-
-
-
-
-
-
-
+ ${buildNotifyEntryHtml(null)}
@@ -3435,6 +3489,8 @@
document.body.appendChild(overlay);
const themePageBtn = panel.querySelector('[data-open-theme-page]');
if (themePageBtn) themePageBtn.addEventListener('click', openThemeSubpage);
+ const notifyPageBtn2 = panel.querySelector('[data-open-notify-page]');
+ if (notifyPageBtn2) notifyPageBtn2.addEventListener('click', openNotifySubpage);
// === Model Config UI ===
const modelModeSelect = panel.querySelector('#model-mode');
@@ -3696,64 +3752,10 @@
renderModelCustomArea();
};
- // === Notify Config UI ===
- const providerSelect = panel.querySelector('#notify-provider');
- const fieldsDiv = panel.querySelector('#notify-fields');
- const summaryArea = panel.querySelector('#notify-summary-area');
- const statusDiv = panel.querySelector('#notify-status');
+ // === Notify Config UI (moved to subpage) ===
+ // notify config is handled by openNotifySubpage()
+
const closeBtn = panel.querySelector('.settings-close');
- const testBtn = panel.querySelector('#notify-test-btn');
- const saveBtn = panel.querySelector('#notify-save-btn');
-
- let currentConfig = null;
-
- function renderFields(provider) {
- renderNotifyFields(fieldsDiv, currentConfig, provider);
- if (summaryArea) {
- summaryArea.innerHTML = buildSummarySettingsHtml(currentConfig);
- bindSummarySettingsEvents(panel);
- }
- }
-
- providerSelect.addEventListener('change', () => renderFields(providerSelect.value));
-
- function collectConfig() {
- return collectNotifyConfigFromPanel(panel, currentConfig, providerSelect.value);
- }
-
- function showStatus(msg, type) {
- statusDiv.textContent = msg;
- statusDiv.className = 'settings-status ' + type;
- }
-
- _onNotifyConfig = (config) => {
- currentConfig = config;
- providerSelect.value = config.provider || 'off';
- renderFields(config.provider || 'off');
- };
-
- _onNotifyTestResult = (msg) => {
- showStatus(msg.message, msg.success ? 'success' : 'error');
- };
-
- closeBtn.addEventListener('click', hideSettingsPanel);
- overlay.addEventListener('click', (e) => { if (e.target === overlay) hideSettingsPanel(); });
-
- testBtn.addEventListener('click', () => {
- // Save first then test
- const config = collectConfig();
- send({ type: 'save_notify_config', config });
- showStatus('正在发送测试消息...', '');
- send({ type: 'test_notify' });
- });
-
- saveBtn.addEventListener('click', () => {
- const config = collectConfig();
- send({ type: 'save_notify_config', config });
- showStatus('已保存', 'success');
- });
-
- // Password change button -> opens modal
const pwOpenModalBtn = panel.querySelector('#pw-open-modal-btn');
pwOpenModalBtn.addEventListener('click', openPasswordModal);
@@ -3786,6 +3788,9 @@
const _origOnUpdateInfo = window._ccOnUpdateInfo;
window._ccOnUpdateInfo = (info) => { if (_onUpdateInfo) _onUpdateInfo(info); };
+ closeBtn.addEventListener('click', hideSettingsPanel);
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) hideSettingsPanel(); });
+
document.addEventListener('keydown', _settingsEscape);
}