Merge pull request #2 from Jasonzhu1207/v0/jasonzhu081207-4751-3942175b

Add AI insights service and settings tab
This commit is contained in:
Jason
2026-04-06 00:12:13 +08:00
committed by GitHub
2 changed files with 33 additions and 25 deletions

View File

@@ -17,7 +17,7 @@ import https from 'https'
import http from 'http' import http from 'http'
import { URL } from 'url' import { URL } from 'url'
import { ConfigService } from './config' import { ConfigService } from './config'
import { chatService } from './chatService' import { chatService, ChatSession, Message } from './chatService'
import { showNotification } from '../windows/notificationWindow' import { showNotification } from '../windows/notificationWindow'
// ─── 常量 ──────────────────────────────────────────────────────────────────── // ─── 常量 ────────────────────────────────────────────────────────────────────
@@ -47,12 +47,6 @@ interface TodayTriggerRecord {
timestamps: number[] timestamps: number[]
} }
interface InsightResult {
sessionId: string
displayName: string
insight: string
}
// ─── 工具函数 ───────────────────────────────────────────────────────────────── // ─── 工具函数 ─────────────────────────────────────────────────────────────────
/** /**
@@ -112,20 +106,21 @@ function callApi(
stream: false stream: false
}) })
const options: https.RequestOptions = { const options = {
hostname: urlObj.hostname, hostname: urlObj.hostname,
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80), port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
path: urlObj.pathname + urlObj.search, path: urlObj.pathname + urlObj.search,
method: 'POST', method: 'POST' as const,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body), 'Content-Length': Buffer.byteLength(body).toString(),
Authorization: `Bearer ${apiKey}` Authorization: `Bearer ${apiKey}`
} }
} }
const transport = urlObj.protocol === 'https:' ? https : http const isHttps = urlObj.protocol === 'https:'
const req = (transport as typeof https).request(options, (res) => { const requestFn = isHttps ? https.request : http.request
const req = requestFn(options, (res) => {
let data = '' let data = ''
res.on('data', (chunk) => { data += chunk }) res.on('data', (chunk) => { data += chunk })
res.on('end', () => { res.on('end', () => {
@@ -193,9 +188,18 @@ class InsightService {
stop(): void { stop(): void {
this.started = false this.started = false
if (this.dbDebounceTimer) { clearTimeout(this.dbDebounceTimer); this.dbDebounceTimer = null } if (this.dbDebounceTimer !== null) {
if (this.silenceScanTimer) { clearInterval(this.silenceScanTimer); this.silenceScanTimer = null } clearTimeout(this.dbDebounceTimer)
if (this.silenceInitialDelayTimer) { clearTimeout(this.silenceInitialDelayTimer); this.silenceInitialDelayTimer = null } this.dbDebounceTimer = null
}
if (this.silenceScanTimer !== null) {
clearInterval(this.silenceScanTimer)
this.silenceScanTimer = null
}
if (this.silenceInitialDelayTimer !== null) {
clearTimeout(this.silenceInitialDelayTimer)
this.silenceInitialDelayTimer = null
}
console.log('[InsightService] 已停止') console.log('[InsightService] 已停止')
} }
@@ -207,7 +211,9 @@ class InsightService {
if (!this.started) return if (!this.started) return
if (!this.isEnabled()) return if (!this.isEnabled()) return
if (this.dbDebounceTimer) clearTimeout(this.dbDebounceTimer) if (this.dbDebounceTimer !== null) {
clearTimeout(this.dbDebounceTimer)
}
this.dbDebounceTimer = setTimeout(() => { this.dbDebounceTimer = setTimeout(() => {
this.dbDebounceTimer = null this.dbDebounceTimer = null
void this.analyzeRecentActivity() void this.analyzeRecentActivity()
@@ -320,14 +326,15 @@ class InsightService {
const sessionsResult = await chatService.getSessions() const sessionsResult = await chatService.getSessions()
if (!sessionsResult.success || !sessionsResult.sessions) return if (!sessionsResult.success || !sessionsResult.sessions) return
const sessions = sessionsResult.sessions as any[] const sessions: ChatSession[] = sessionsResult.sessions
for (const session of sessions) { for (const session of sessions) {
const sessionId = String(session.username || '').trim() const sessionId = session.username?.trim() || ''
if (!sessionId || sessionId.endsWith('@chatroom')) continue // 跳过群聊 if (!sessionId || sessionId.endsWith('@chatroom')) continue // 跳过群聊
if (sessionId.toLowerCase().includes('placeholder')) continue if (sessionId.toLowerCase().includes('placeholder')) continue
const lastTimestamp = Number(session.lastTimestamp || 0) * (String(session.lastTimestamp).length <= 10 ? 1000 : 1) // lastTimestamp 单位是秒,需要转换为毫秒
const lastTimestamp = (session.lastTimestamp || 0) * 1000
if (!lastTimestamp || lastTimestamp <= 0) continue if (!lastTimestamp || lastTimestamp <= 0) continue
const silentMs = now - lastTimestamp const silentMs = now - lastTimestamp
@@ -366,18 +373,18 @@ class InsightService {
const sessionsResult = await chatService.getSessions() const sessionsResult = await chatService.getSessions()
if (!sessionsResult.success || !sessionsResult.sessions) return if (!sessionsResult.success || !sessionsResult.sessions) return
const sessions = sessionsResult.sessions as any[] const sessions: ChatSession[] = sessionsResult.sessions
// 只取最近有活动的前 5 个会话(排除群聊以降低噪音,可按需调整) // 只取最近有活动的前 5 个会话(排除群聊以降低噪音,可按需调整)
const candidates = sessions const candidates = sessions
.filter((s) => { .filter((s) => {
const id = String(s.username || '').trim() const id = s.username?.trim() || ''
return id && !id.endsWith('@chatroom') && !id.toLowerCase().includes('placeholder') return id && !id.endsWith('@chatroom') && !id.toLowerCase().includes('placeholder')
}) })
.slice(0, 5) .slice(0, 5)
for (const session of candidates) { for (const session of candidates) {
await this.generateInsightForSession({ await this.generateInsightForSession({
sessionId: String(session.username || '').trim(), sessionId: session.username?.trim() || '',
displayName: session.displayName || session.username, displayName: session.displayName || session.username,
triggerReason: 'activity' triggerReason: 'activity'
}) })
@@ -418,13 +425,14 @@ class InsightService {
try { try {
const msgsResult = await chatService.getLatestMessages(sessionId, MAX_CONTEXT_MESSAGES) const msgsResult = await chatService.getLatestMessages(sessionId, MAX_CONTEXT_MESSAGES)
if (msgsResult.success && msgsResult.messages && msgsResult.messages.length > 0) { if (msgsResult.success && msgsResult.messages && msgsResult.messages.length > 0) {
const msgLines = (msgsResult.messages as any[]).map((m) => { const messages: Message[] = msgsResult.messages
const msgLines = messages.map((m) => {
const sender = m.isSend === 1 ? '我' : (displayName || sessionId) const sender = m.isSend === 1 ? '我' : (displayName || sessionId)
const content = m.rawContent || m.parsedContent || '[非文字消息]' const content = m.rawContent || m.parsedContent || '[非文字消息]'
const time = new Date(Number(m.createTime) * 1000).toLocaleString('zh-CN') const time = new Date(Number(m.createTime) * 1000).toLocaleString('zh-CN')
return `[${time}] ${sender}${content}` return `[${time}] ${sender}${content}`
}) })
contextSection = `\n\n期对话记录(最近 ${msgLines.length} 条):\n${msgLines.join('\n')}` contextSection = `\n\n<EFBFBD><EFBFBD>期对话记录(最近 ${msgLines.length} 条):\n${msgLines.join('\n')}`
} }
} catch (e) { } catch (e) {
console.warn('[InsightService] 拉取上下文失败:', e) console.warn('[InsightService] 拉取上下文失败:', e)

View File

@@ -77,7 +77,7 @@
"appId": "com.WeFlow.app", "appId": "com.WeFlow.app",
"publish": { "publish": {
"provider": "github", "provider": "github",
"owner": "hicccc77", "owner": "Jasonzhu1207",
"repo": "WeFlow", "repo": "WeFlow",
"releaseType": "release" "releaseType": "release"
}, },