mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-06 07:26:48 +00:00
feat: add AI insight notification toggle
This commit is contained in:
@@ -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: [],
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
BIN
public/assets/insight/AI_Insight.png
Normal file
BIN
public/assets/insight/AI_Insight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 MiB |
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user