feat: add AI insight notification toggle

This commit is contained in:
Jason
2026-05-05 12:08:32 +08:00
parent becec65ee3
commit b4758d690b
6 changed files with 87 additions and 27 deletions

View File

@@ -58,6 +58,7 @@ interface ConfigSchema {
// 通知 // 通知
notificationEnabled: boolean notificationEnabled: boolean
aiInsightNotificationEnabled: boolean
notificationPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' notificationPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'
notificationFilterMode: 'all' | 'whitelist' | 'blacklist' notificationFilterMode: 'all' | 'whitelist' | 'blacklist'
notificationFilterList: string[] notificationFilterList: string[]
@@ -188,6 +189,7 @@ export class ConfigService {
ignoredUpdateVersion: '', ignoredUpdateVersion: '',
updateChannel: 'auto', updateChannel: 'auto',
notificationEnabled: true, notificationEnabled: true,
aiInsightNotificationEnabled: true,
notificationPosition: 'top-right', notificationPosition: 'top-right',
notificationFilterMode: 'all', notificationFilterMode: 'all',
notificationFilterList: [], notificationFilterList: [],

View File

@@ -18,11 +18,12 @@ import http from 'http'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { URL } from 'url' import { URL } from 'url'
import { app, Notification } from 'electron' import { app } from 'electron'
import { ConfigService } from './config' import { ConfigService } from './config'
import { chatService, ChatSession, Message } from './chatService' import { chatService, ChatSession, Message } from './chatService'
import { snsService } from './snsService' import { snsService } from './snsService'
import { weiboService } from './social/weiboService' import { weiboService } from './social/weiboService'
import { showNotification } from '../windows/notificationWindow'
// ─── 常量 ──────────────────────────────────────────────────────────────────── // ─── 常量 ────────────────────────────────────────────────────────────────────
@@ -41,6 +42,7 @@ const API_MAX_TOKENS_DEFAULT = 200
const API_MAX_TOKENS_MIN = 1 const API_MAX_TOKENS_MIN = 1
const API_MAX_TOKENS_MAX = 65_535 const API_MAX_TOKENS_MAX = 65_535
const API_TEMPERATURE = 0.7 const API_TEMPERATURE = 0.7
const INSIGHT_NOTIFICATION_AVATAR_URL = './assets/insight/AI_Insight.png'
/** 沉默天数阈值默认值 */ /** 沉默天数阈值默认值 */
const DEFAULT_SILENCE_DAYS = 3 const DEFAULT_SILENCE_DAYS = 3
@@ -518,7 +520,13 @@ class InsightService {
displayName, displayName,
triggerReason: 'activity' triggerReason: 'activity'
}) })
return { success: true, message: `已向「${displayName}」发送测试见解,请查看右下角弹窗` } const notificationEnabled = this.config.get('aiInsightNotificationEnabled') !== false
return {
success: true,
message: notificationEnabled
? `已向「${displayName}」发送测试见解,请查看通知弹窗`
: `已生成「${displayName}」的测试见解AI 见解消息通知当前已关闭`
}
} catch (e) { } catch (e) {
return { success: false, message: `测试失败:${(e as Error).message}` } return { success: false, message: `测试失败:${(e as Error).message}` }
} }
@@ -1250,14 +1258,20 @@ ${topMentionText}
const insight = result.slice(0, 120) const insight = result.slice(0, 120)
const notifTitle = `见解 · ${resolvedDisplayName}` const notifTitle = `见解 · ${resolvedDisplayName}`
const insightNotificationEnabled = this.config.get('aiInsightNotificationEnabled') !== false
if (insightNotificationEnabled) {
insightLog('INFO', `推送通知 → ${resolvedDisplayName}: ${insight}`) insightLog('INFO', `推送通知 → ${resolvedDisplayName}: ${insight}`)
// 渠道一:Electron 原生系统通知 // 渠道一:应用内通知窗口。AI 见解使用独立通知开关,不受新消息通知开关和会话过滤影响。
if (Notification.isSupported()) { await showNotification({
const notif = new Notification({ title: notifTitle, body: insight, silent: false }) title: notifTitle,
notif.show() content: insight,
avatarUrl: INSIGHT_NOTIFICATION_AVATAR_URL,
sessionId,
channel: 'ai-insight'
})
} else { } else {
insightLog('WARN', '当前系统不支持原生通知') insightLog('INFO', `AI 见解消息通知已关闭,跳过应用通知 → ${resolvedDisplayName}: ${insight}`)
} }
// 渠道二Telegram Bot 推送(可选) // 渠道二Telegram Bot 推送(可选)
@@ -1278,7 +1292,7 @@ ${topMentionText}
} }
} }
insightLog('INFO', ` ${resolvedDisplayName} 推送见解`) insightLog('INFO', `完成 ${resolvedDisplayName} 的见解处理`)
this.recordTrigger(sessionId) this.recordTrigger(sessionId)
} catch (e) { } catch (e) {
insightDebugSection( insightDebugSection(

View File

@@ -109,13 +109,20 @@ export function createNotificationWindow() {
export async function showNotification(data: any) { export async function showNotification(data: any) {
// 先检查配置 // 先检查配置
const config = ConfigService.getInstance(); const config = ConfigService.getInstance();
const sessionId = typeof data.sessionId === "string" ? data.sessionId : "";
const channel = typeof data.channel === "string" ? data.channel : "";
const isAiInsightNotification = channel === "ai-insight";
if (isAiInsightNotification) {
const enabled = await config.get("aiInsightNotificationEnabled");
if (enabled === false) return; // 默认为 true
} else {
const enabled = await config.get("notificationEnabled"); const enabled = await config.get("notificationEnabled");
if (enabled === false) return; // 默认为 true if (enabled === false) return; // 默认为 true
// 检查会话过滤 // 检查会话过滤
const filterMode = config.get("notificationFilterMode") || "all"; const filterMode = config.get("notificationFilterMode") || "all";
const filterList = config.get("notificationFilterList") || []; const filterList = config.get("notificationFilterList") || [];
const sessionId = typeof data.sessionId === "string" ? data.sessionId : "";
// 系统通知(如 "WeFlow 准备就绪")不是聊天消息,不应受会话白/黑名单影响 // 系统通知(如 "WeFlow 准备就绪")不是聊天消息,不应受会话白/黑名单影响
const isSystemNotification = sessionId.startsWith("weflow-"); const isSystemNotification = sessionId.startsWith("weflow-");
@@ -130,6 +137,7 @@ export async function showNotification(data: any) {
return; return;
} }
} }
}
// Linux 使用 D-Bus 通知 // Linux 使用 D-Bus 通知
if (isLinux) { if (isLinux) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

View File

@@ -199,6 +199,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const [transcribeLanguages, setTranscribeLanguages] = useState<string[]>(['zh']) const [transcribeLanguages, setTranscribeLanguages] = useState<string[]>(['zh'])
const [notificationEnabled, setNotificationEnabled] = useState(true) const [notificationEnabled, setNotificationEnabled] = useState(true)
const [aiInsightNotificationEnabled, setAiInsightNotificationEnabled] = useState(true)
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right') const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right')
const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all') const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all')
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([]) const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
@@ -458,6 +459,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const savedAutoTranscribe = await configService.getAutoTranscribeVoice() const savedAutoTranscribe = await configService.getAutoTranscribeVoice()
const savedTranscribeLanguages = await configService.getTranscribeLanguages() const savedTranscribeLanguages = await configService.getTranscribeLanguages()
const savedNotificationEnabled = await configService.getNotificationEnabled() const savedNotificationEnabled = await configService.getNotificationEnabled()
const savedAiInsightNotificationEnabled = await configService.getAiInsightNotificationEnabled()
const savedNotificationPosition = await configService.getNotificationPosition() const savedNotificationPosition = await configService.getNotificationPosition()
const savedNotificationFilterMode = await configService.getNotificationFilterMode() const savedNotificationFilterMode = await configService.getNotificationFilterMode()
const savedNotificationFilterList = await configService.getNotificationFilterList() const savedNotificationFilterList = await configService.getNotificationFilterList()
@@ -512,6 +514,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
setTranscribeLanguages(savedTranscribeLanguages) setTranscribeLanguages(savedTranscribeLanguages)
setNotificationEnabled(savedNotificationEnabled) setNotificationEnabled(savedNotificationEnabled)
setAiInsightNotificationEnabled(savedAiInsightNotificationEnabled)
setNotificationPosition(savedNotificationPosition) setNotificationPosition(savedNotificationPosition)
setNotificationFilterMode(savedNotificationFilterMode) setNotificationFilterMode(savedNotificationFilterMode)
setNotificationFilterList(savedNotificationFilterList) setNotificationFilterList(savedNotificationFilterList)
@@ -1903,6 +1906,29 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</div> </div>
</div> </div>
<div className="form-group">
<label>AI </label>
<span className="form-hint"> AI Telegram </span>
<div className="log-toggle-line">
<span className="log-status">{aiInsightNotificationEnabled ? '已开启' : '已关闭'}</span>
<label className="switch" htmlFor="ai-insight-notification-enabled-toggle">
<input
id="ai-insight-notification-enabled-toggle"
className="switch-input"
type="checkbox"
checked={aiInsightNotificationEnabled}
onChange={async (e) => {
const val = e.target.checked
setAiInsightNotificationEnabled(val)
await configService.setAiInsightNotificationEnabled(val)
showMessage(val ? '已开启 AI 见解消息通知' : '已关闭 AI 见解消息通知', true)
}}
/>
<span className="switch-slider" />
</label>
</div>
</div>
<div className="form-group"> <div className="form-group">
<label></label> <label></label>
<span className="form-hint"></span> <span className="form-hint"></span>
@@ -3209,7 +3235,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="form-group"> <div className="form-group">
<label>AI </label> <label>AI </label>
<span className="form-hint"> <span className="form-hint">
AI AI
</span> </span>
<div className="log-toggle-line"> <div className="log-toggle-line">
<span className="log-status">{aiInsightEnabled ? '已开启' : '已关闭'}</span> <span className="log-status">{aiInsightEnabled ? '已开启' : '已关闭'}</span>

View File

@@ -66,6 +66,7 @@ export const CONFIG_KEYS = {
// 通知 // 通知
NOTIFICATION_ENABLED: 'notificationEnabled', NOTIFICATION_ENABLED: 'notificationEnabled',
AI_INSIGHT_NOTIFICATION_ENABLED: 'aiInsightNotificationEnabled',
NOTIFICATION_POSITION: 'notificationPosition', NOTIFICATION_POSITION: 'notificationPosition',
NOTIFICATION_FILTER_MODE: 'notificationFilterMode', NOTIFICATION_FILTER_MODE: 'notificationFilterMode',
NOTIFICATION_FILTER_LIST: 'notificationFilterList', NOTIFICATION_FILTER_LIST: 'notificationFilterList',
@@ -1677,6 +1678,15 @@ export async function setNotificationEnabled(enabled: boolean): Promise<void> {
await config.set(CONFIG_KEYS.NOTIFICATION_ENABLED, enabled) await config.set(CONFIG_KEYS.NOTIFICATION_ENABLED, enabled)
} }
export async function getAiInsightNotificationEnabled(): Promise<boolean> {
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_NOTIFICATION_ENABLED)
return value !== false
}
export async function setAiInsightNotificationEnabled(enabled: boolean): Promise<void> {
await config.set(CONFIG_KEYS.AI_INSIGHT_NOTIFICATION_ENABLED, enabled)
}
// 获取通知位置 // 获取通知位置
export async function getNotificationPosition(): Promise<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'> { export async function getNotificationPosition(): Promise<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'> {
const value = await config.get(CONFIG_KEYS.NOTIFICATION_POSITION) const value = await config.get(CONFIG_KEYS.NOTIFICATION_POSITION)