feat: 通知AI摘要 + 通知配置收进二级菜单 (v1.2.9)
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# 更新记录
|
# 更新记录
|
||||||
|
|
||||||
|
## v1.2.9
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
- **通知 AI 摘要** — 任务完成时调用 Claude API 生成摘要内容推送,支持正常完成/异常/上下文压缩等多种情况分类,摘要 API 凭证可独立配置或复用活跃 Claude 模板/Codex Profile,各渠道按字符限制自动截断,摘要失败时降级为原始信息
|
||||||
|
- **通知配置收进二级菜单** — Claude 和 Codex 设置面板中的通知区域改为 nav-card 入口,点击进入独立子页,与主题设置风格统一
|
||||||
|
|
||||||
## v1.2.8
|
## v1.2.8
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
277
public/app.js
277
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 `
|
||||||
|
<div class="settings-section-title">通知</div>
|
||||||
|
<button class="settings-nav-card" type="button" data-open-notify-page>
|
||||||
|
<span class="settings-nav-card-main">
|
||||||
|
<span class="settings-nav-card-title">通知设置</span>
|
||||||
|
<span class="settings-nav-card-meta" data-notify-summary>${escapeHtml(meta)}</span>
|
||||||
|
</span>
|
||||||
|
<span class="settings-nav-card-arrow" aria-hidden="true">›</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="settings-header settings-subpage-header">
|
||||||
|
<button class="settings-back" type="button" aria-label="返回">‹</button>
|
||||||
|
<div class="settings-subpage-copy">
|
||||||
|
<div class="settings-subpage-kicker">Notification</div>
|
||||||
|
<h3>通知设置</h3>
|
||||||
|
</div>
|
||||||
|
<button class="settings-close" type="button" title="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-field">
|
||||||
|
<label>通知方式</label>
|
||||||
|
<select class="settings-select" id="notify-provider">
|
||||||
|
${PROVIDER_OPTIONS.map(o => `<option value="${o.value}">${escapeHtml(o.label)}</option>`).join('')}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="notify-fields"></div>
|
||||||
|
<div id="notify-summary-area"></div>
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button class="btn-test" id="notify-test-btn">测试</button>
|
||||||
|
<button class="btn-save" id="notify-save-btn">保存</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-status" id="notify-status"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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() {
|
function openThemeSubpage() {
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'settings-overlay settings-subpage-overlay';
|
overlay.className = 'settings-overlay settings-subpage-overlay';
|
||||||
@@ -3025,7 +3153,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showCodexSettingsPanel() {
|
function showCodexSettingsPanel() {
|
||||||
send({ type: 'get_notify_config' });
|
|
||||||
send({ type: 'get_codex_config' });
|
send({ type: 'get_codex_config' });
|
||||||
|
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
@@ -3060,20 +3187,7 @@
|
|||||||
|
|
||||||
<div class="settings-divider"></div>
|
<div class="settings-divider"></div>
|
||||||
|
|
||||||
<div class="settings-section-title">通知设置</div>
|
${buildNotifyEntryHtml(null)}
|
||||||
<div class="settings-field">
|
|
||||||
<label>通知方式</label>
|
|
||||||
<select class="settings-select" id="notify-provider">
|
|
||||||
${PROVIDER_OPTIONS.map(o => `<option value="${o.value}">${escapeHtml(o.label)}</option>`).join('')}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="notify-fields"></div>
|
|
||||||
<div id="notify-summary-area"></div>
|
|
||||||
<div class="settings-actions">
|
|
||||||
<button class="btn-test" id="notify-test-btn">测试</button>
|
|
||||||
<button class="btn-save" id="notify-save-btn">保存</button>
|
|
||||||
</div>
|
|
||||||
<div class="settings-status" id="notify-status"></div>
|
|
||||||
|
|
||||||
<div class="settings-divider"></div>
|
<div class="settings-divider"></div>
|
||||||
|
|
||||||
@@ -3089,6 +3203,8 @@
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
const themePageBtn = panel.querySelector('[data-open-theme-page]');
|
const themePageBtn = panel.querySelector('[data-open-theme-page]');
|
||||||
if (themePageBtn) themePageBtn.addEventListener('click', openThemeSubpage);
|
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 closeBtn = panel.querySelector('.settings-close');
|
||||||
const codexModeSelect = panel.querySelector('#codex-mode');
|
const codexModeSelect = panel.querySelector('#codex-mode');
|
||||||
@@ -3096,17 +3212,10 @@
|
|||||||
const codexStatus = panel.querySelector('#codex-status');
|
const codexStatus = panel.querySelector('#codex-status');
|
||||||
const codexSaveBtn = panel.querySelector('#codex-save-btn');
|
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 pwOpenModalBtn = panel.querySelector('#pw-open-modal-btn');
|
||||||
const checkUpdateBtn = panel.querySelector('#check-update-btn');
|
const checkUpdateBtn = panel.querySelector('#check-update-btn');
|
||||||
const updateStatusEl = panel.querySelector('#update-status');
|
const updateStatusEl = panel.querySelector('#update-status');
|
||||||
|
|
||||||
let currentNotifyConfig = null;
|
|
||||||
let currentCodexConfig = null;
|
let currentCodexConfig = null;
|
||||||
let codexEditingProfiles = [];
|
let codexEditingProfiles = [];
|
||||||
let codexActiveProfile = '';
|
let codexActiveProfile = '';
|
||||||
@@ -3117,23 +3226,6 @@
|
|||||||
codexStatus.className = 'settings-status ' + (type || '');
|
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() {
|
function renderCodexProfileArea() {
|
||||||
const mode = codexModeSelect.value;
|
const mode = codexModeSelect.value;
|
||||||
if (mode === 'local') {
|
if (mode === 'local') {
|
||||||
@@ -3293,17 +3385,6 @@
|
|||||||
renderCodexProfileArea();
|
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);
|
codexModeSelect.addEventListener('change', renderCodexProfileArea);
|
||||||
|
|
||||||
codexSaveBtn.addEventListener('click', () => {
|
codexSaveBtn.addEventListener('click', () => {
|
||||||
@@ -3321,19 +3402,6 @@
|
|||||||
showCodexStatus('已保存', 'success');
|
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);
|
pwOpenModalBtn.addEventListener('click', openPasswordModal);
|
||||||
|
|
||||||
checkUpdateBtn.addEventListener('click', () => {
|
checkUpdateBtn.addEventListener('click', () => {
|
||||||
@@ -3369,8 +3437,7 @@
|
|||||||
showCodexSettingsPanel();
|
showCodexSettingsPanel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Request current configs
|
// Request current configs (notify config is loaded on demand inside subpage)
|
||||||
send({ type: 'get_notify_config' });
|
|
||||||
send({ type: 'get_model_config' });
|
send({ type: 'get_model_config' });
|
||||||
|
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
@@ -3406,20 +3473,7 @@
|
|||||||
|
|
||||||
<div class="settings-divider"></div>
|
<div class="settings-divider"></div>
|
||||||
|
|
||||||
<div class="settings-section-title">通知设置</div>
|
${buildNotifyEntryHtml(null)}
|
||||||
<div class="settings-field">
|
|
||||||
<label>通知方式</label>
|
|
||||||
<select class="settings-select" id="notify-provider">
|
|
||||||
${PROVIDER_OPTIONS.map(o => `<option value="${o.value}">${escapeHtml(o.label)}</option>`).join('')}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="notify-fields"></div>
|
|
||||||
<div id="notify-summary-area"></div>
|
|
||||||
<div class="settings-actions">
|
|
||||||
<button class="btn-test" id="notify-test-btn">测试</button>
|
|
||||||
<button class="btn-save" id="notify-save-btn">保存</button>
|
|
||||||
</div>
|
|
||||||
<div class="settings-status" id="notify-status"></div>
|
|
||||||
|
|
||||||
<div class="settings-divider"></div>
|
<div class="settings-divider"></div>
|
||||||
|
|
||||||
@@ -3435,6 +3489,8 @@
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
const themePageBtn = panel.querySelector('[data-open-theme-page]');
|
const themePageBtn = panel.querySelector('[data-open-theme-page]');
|
||||||
if (themePageBtn) themePageBtn.addEventListener('click', openThemeSubpage);
|
if (themePageBtn) themePageBtn.addEventListener('click', openThemeSubpage);
|
||||||
|
const notifyPageBtn2 = panel.querySelector('[data-open-notify-page]');
|
||||||
|
if (notifyPageBtn2) notifyPageBtn2.addEventListener('click', openNotifySubpage);
|
||||||
|
|
||||||
// === Model Config UI ===
|
// === Model Config UI ===
|
||||||
const modelModeSelect = panel.querySelector('#model-mode');
|
const modelModeSelect = panel.querySelector('#model-mode');
|
||||||
@@ -3696,64 +3752,10 @@
|
|||||||
renderModelCustomArea();
|
renderModelCustomArea();
|
||||||
};
|
};
|
||||||
|
|
||||||
// === Notify Config UI ===
|
// === Notify Config UI (moved to subpage) ===
|
||||||
const providerSelect = panel.querySelector('#notify-provider');
|
// notify config is handled by openNotifySubpage()
|
||||||
const fieldsDiv = panel.querySelector('#notify-fields');
|
|
||||||
const summaryArea = panel.querySelector('#notify-summary-area');
|
|
||||||
const statusDiv = panel.querySelector('#notify-status');
|
|
||||||
const closeBtn = panel.querySelector('.settings-close');
|
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');
|
const pwOpenModalBtn = panel.querySelector('#pw-open-modal-btn');
|
||||||
pwOpenModalBtn.addEventListener('click', openPasswordModal);
|
pwOpenModalBtn.addEventListener('click', openPasswordModal);
|
||||||
|
|
||||||
@@ -3786,6 +3788,9 @@
|
|||||||
const _origOnUpdateInfo = window._ccOnUpdateInfo;
|
const _origOnUpdateInfo = window._ccOnUpdateInfo;
|
||||||
window._ccOnUpdateInfo = (info) => { if (_onUpdateInfo) _onUpdateInfo(info); };
|
window._ccOnUpdateInfo = (info) => { if (_onUpdateInfo) _onUpdateInfo(info); };
|
||||||
|
|
||||||
|
closeBtn.addEventListener('click', hideSettingsPanel);
|
||||||
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) hideSettingsPanel(); });
|
||||||
|
|
||||||
document.addEventListener('keydown', _settingsEscape);
|
document.addEventListener('keydown', _settingsEscape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user