mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-08 15:08:44 +00:00
Merge pull request #7 from Jasonzhu1207/v0/jasonzhu081207-4751-e705ab05
Enable AI insights and system-native notifications
This commit is contained in:
@@ -24,8 +24,11 @@ import { chatService, ChatSession, Message } from './chatService'
|
|||||||
|
|
||||||
// ─── 常量 ────────────────────────────────────────────────────────────────────
|
// ─── 常量 ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** DB 变更防抖延迟(毫秒) */
|
/**
|
||||||
const DB_CHANGE_DEBOUNCE_MS = 500
|
* DB 变更防抖延迟(毫秒)。
|
||||||
|
* 设为 2s:微信写库通常是批量操作,500ms 过短会在开机/重连时产生大量连续触发。
|
||||||
|
*/
|
||||||
|
const DB_CHANGE_DEBOUNCE_MS = 2000
|
||||||
|
|
||||||
/** 首次沉默扫描延迟(毫秒),避免启动期间抢占资源 */
|
/** 首次沉默扫描延迟(毫秒),避免启动期间抢占资源 */
|
||||||
const SILENCE_SCAN_INITIAL_DELAY_MS = 3 * 60 * 1000
|
const SILENCE_SCAN_INITIAL_DELAY_MS = 3 * 60 * 1000
|
||||||
@@ -208,6 +211,15 @@ class InsightService {
|
|||||||
*/
|
*/
|
||||||
private lastSeenTimestamp: Map<string, number> = new Map()
|
private lastSeenTimestamp: Map<string, number> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地会话快照缓存,避免 analyzeRecentActivity 在每次 DB 变更时都做全量读取。
|
||||||
|
* 首次调用时填充,此后只在沉默扫描里刷新(沉默扫描间隔更长,更合适做全量刷新)。
|
||||||
|
*/
|
||||||
|
private sessionCache: ChatSession[] | null = null
|
||||||
|
/** sessionCache 最后刷新时间戳(ms),超过 5 分钟强制重新拉取 */
|
||||||
|
private sessionCacheAt = 0
|
||||||
|
private static readonly SESSION_CACHE_TTL_MS = 5 * 60 * 1000
|
||||||
|
|
||||||
private started = false
|
private started = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -230,7 +242,7 @@ class InsightService {
|
|||||||
this.dbDebounceTimer = null
|
this.dbDebounceTimer = null
|
||||||
}
|
}
|
||||||
if (this.silenceScanTimer !== null) {
|
if (this.silenceScanTimer !== null) {
|
||||||
clearInterval(this.silenceScanTimer)
|
clearTimeout(this.silenceScanTimer)
|
||||||
this.silenceScanTimer = null
|
this.silenceScanTimer = null
|
||||||
}
|
}
|
||||||
if (this.silenceInitialDelayTimer !== null) {
|
if (this.silenceInitialDelayTimer !== null) {
|
||||||
@@ -358,6 +370,29 @@ class InsightService {
|
|||||||
return whitelist.includes(sessionId)
|
return whitelist.includes(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话列表,优先使用缓存(5 分钟 TTL)。
|
||||||
|
* 缓存命中时不访问 DB,显著减少对主线程的占用。
|
||||||
|
*/
|
||||||
|
private async getSessionsCached(forceRefresh = false): Promise<ChatSession[]> {
|
||||||
|
const now = Date.now()
|
||||||
|
if (
|
||||||
|
!forceRefresh &&
|
||||||
|
this.sessionCache !== null &&
|
||||||
|
now - this.sessionCacheAt < InsightService.SESSION_CACHE_TTL_MS
|
||||||
|
) {
|
||||||
|
return this.sessionCache
|
||||||
|
}
|
||||||
|
const connectResult = await chatService.connect()
|
||||||
|
if (!connectResult.success) return this.sessionCache ?? []
|
||||||
|
const result = await chatService.getSessions()
|
||||||
|
if (result.success && result.sessions) {
|
||||||
|
this.sessionCache = result.sessions as ChatSession[]
|
||||||
|
this.sessionCacheAt = now
|
||||||
|
}
|
||||||
|
return this.sessionCache ?? []
|
||||||
|
}
|
||||||
|
|
||||||
private resetIfNewDay(): void {
|
private resetIfNewDay(): void {
|
||||||
const todayStart = getStartOfDay()
|
const todayStart = getStartOfDay()
|
||||||
if (todayStart > this.todayDate) {
|
if (todayStart > this.todayDate) {
|
||||||
@@ -426,19 +461,13 @@ class InsightService {
|
|||||||
|
|
||||||
insightLog('INFO', `沉默阈值:${silenceDays} 天`)
|
insightLog('INFO', `沉默阈值:${silenceDays} 天`)
|
||||||
|
|
||||||
const connectResult = await chatService.connect()
|
// 沉默扫描间隔较长,强制刷新缓存以获取最新数据
|
||||||
if (!connectResult.success) {
|
const sessions = await this.getSessionsCached(true)
|
||||||
insightLog('WARN', '数据库连接失败,跳过沉默扫描')
|
if (sessions.length === 0) {
|
||||||
|
insightLog('WARN', '获取会话列表失败,跳过沉默扫描')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionsResult = await chatService.getSessions()
|
|
||||||
if (!sessionsResult.success || !sessionsResult.sessions) {
|
|
||||||
insightLog('WARN', '获取会话列表失败')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessions: ChatSession[] = sessionsResult.sessions
|
|
||||||
insightLog('INFO', `共 ${sessions.length} 个会话,开始过滤...`)
|
insightLog('INFO', `共 ${sessions.length} 个会话,开始过滤...`)
|
||||||
|
|
||||||
let silentCount = 0
|
let silentCount = 0
|
||||||
@@ -489,19 +518,13 @@ class InsightService {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
insightLog('INFO', 'DB 变更防抖触发,开始活跃分析...')
|
insightLog('INFO', 'DB 变更防抖触发,开始活跃分析...')
|
||||||
try {
|
try {
|
||||||
const connectResult = await chatService.connect()
|
// 使用缓存版本,避免每次 DB 变更都做全量读取(5 分钟 TTL)
|
||||||
if (!connectResult.success) {
|
const sessions = await this.getSessionsCached()
|
||||||
insightLog('WARN', '数据库连接失败,跳过活跃分析')
|
if (sessions.length === 0) {
|
||||||
|
insightLog('WARN', '会话缓存为空,跳过活跃分析')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionsResult = await chatService.getSessions()
|
|
||||||
if (!sessionsResult.success || !sessionsResult.sessions) {
|
|
||||||
insightLog('WARN', '获取会话列表失败')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessions: ChatSession[] = sessionsResult.sessions
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
// 从 config 读取冷却分钟数(0 = 无冷却)
|
// 从 config 读取冷却分钟数(0 = 无冷却)
|
||||||
|
|||||||
@@ -864,16 +864,19 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab !== 'antiRevoke') return
|
if (activeTab !== 'antiRevoke' && activeTab !== 'insight') return
|
||||||
let canceled = false
|
let canceled = false
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
|
// 两个 Tab 都需要会话列表;antiRevoke 还需要额外检查防撤回状态
|
||||||
const sessionIds = await ensureAntiRevokeSessionsLoaded()
|
const sessionIds = await ensureAntiRevokeSessionsLoaded()
|
||||||
if (canceled) return
|
if (canceled) return
|
||||||
|
if (activeTab === 'antiRevoke') {
|
||||||
await handleRefreshAntiRevokeStatus(sessionIds)
|
await handleRefreshAntiRevokeStatus(sessionIds)
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
showMessage(`加载防撤回会话失败: ${e?.message || String(e)}`, false)
|
showMessage(`加载会话失败: ${e?.message || String(e)}`, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -1215,7 +1218,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
if (result.success && result.aesKey) {
|
if (result.success && result.aesKey) {
|
||||||
if (typeof result.xorKey === 'number') setImageXorKey(`0x${result.xorKey.toString(16).toUpperCase().padStart(2, '0')}`)
|
if (typeof result.xorKey === 'number') setImageXorKey(`0x${result.xorKey.toString(16).toUpperCase().padStart(2, '0')}`)
|
||||||
setImageAesKey(result.aesKey)
|
setImageAesKey(result.aesKey)
|
||||||
setImageKeyStatus('已获取图片密钥')
|
setImageKeyStatus('已获取图片<EFBFBD><EFBFBD>钥')
|
||||||
showMessage('已自动获取图片密钥', true)
|
showMessage('已自动获取图片密钥', true)
|
||||||
const newXorKey = typeof result.xorKey === 'number' ? result.xorKey : 0
|
const newXorKey = typeof result.xorKey === 'number' ? result.xorKey : 0
|
||||||
const newAesKey = result.aesKey
|
const newAesKey = result.aesKey
|
||||||
@@ -3551,7 +3554,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
<div className="updates-hero-main">
|
<div className="updates-hero-main">
|
||||||
<span className="updates-chip">当前版本</span>
|
<span className="updates-chip">当前版本</span>
|
||||||
<h2>{appVersion || '...'}</h2>
|
<h2>{appVersion || '...'}</h2>
|
||||||
<p>{updateInfo?.hasUpdate ? `发现新版本 v${updateInfo.version}` : '当前已是最新版本,可手动检查更新'}</p>
|
<p>{updateInfo?.hasUpdate ? `发现新版本 v${updateInfo.version}` : '当前已是最新版本,可手动检查更<EFBFBD><EFBFBD><EFBFBD>'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="updates-hero-action">
|
<div className="updates-hero-action">
|
||||||
{updateInfo?.hasUpdate ? (
|
{updateInfo?.hasUpdate ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user