mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-08 07:25:51 +00:00
fix: resolve core bugs and enhance logging for AI insights
Fix aggressive activity analysis and loop bug, add detailed logs, and introduce test trigger button. Co-authored-by: Jason <159670257+Jasonzhu1207@users.noreply.github.com>
This commit is contained in:
@@ -1404,6 +1404,10 @@ function registerIpcHandlers() {
|
|||||||
return insightService.getTodayStats()
|
return insightService.getTodayStats()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('insight:triggerTest', async () => {
|
||||||
|
return insightService.triggerTest()
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('config:clear', async () => {
|
ipcMain.handle('config:clear', async () => {
|
||||||
if (isLaunchAtStartupSupported() && getSystemLaunchAtStartup()) {
|
if (isLaunchAtStartupSupported() && getSystemLaunchAtStartup()) {
|
||||||
const result = setSystemLaunchAtStartup(false)
|
const result = setSystemLaunchAtStartup(false)
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
// AI 见解
|
// AI 见解
|
||||||
insight: {
|
insight: {
|
||||||
testConnection: () => ipcRenderer.invoke('insight:testConnection'),
|
testConnection: () => ipcRenderer.invoke('insight:testConnection'),
|
||||||
getTodayStats: () => ipcRenderer.invoke('insight:getTodayStats')
|
getTodayStats: () => ipcRenderer.invoke('insight:getTodayStats'),
|
||||||
|
triggerTest: () => ipcRenderer.invoke('insight:triggerTest')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ const MAX_CONTEXT_MESSAGES = 40
|
|||||||
/** 沉默天数阈值默认值 */
|
/** 沉默天数阈值默认值 */
|
||||||
const DEFAULT_SILENCE_DAYS = 3
|
const DEFAULT_SILENCE_DAYS = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一会话活跃分析的冷却时间(毫秒),2 小时。
|
||||||
|
* 防止 DB 频繁变更时对同一会话反复调用 API。
|
||||||
|
*/
|
||||||
|
const ACTIVITY_COOLDOWN_MS = 2 * 60 * 60 * 1000
|
||||||
|
|
||||||
// ─── 类型 ────────────────────────────────────────────────────────────────────
|
// ─── 类型 ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface TodayTriggerRecord {
|
interface TodayTriggerRecord {
|
||||||
@@ -171,6 +177,18 @@ class InsightService {
|
|||||||
private todayTriggers: Map<string, TodayTriggerRecord> = new Map()
|
private todayTriggers: Map<string, TodayTriggerRecord> = new Map()
|
||||||
private todayDate = getStartOfDay()
|
private todayDate = getStartOfDay()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 活跃分析冷却记录:sessionId -> 上次分析时间戳(毫秒)
|
||||||
|
* 同一会话 2 小时内不重复触发活跃分析,防止 DB 频繁变更时爆量调用 API。
|
||||||
|
*/
|
||||||
|
private lastActivityAnalysis: Map<string, number> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪每个会话上次见到的最新消息时间戳,用于判断是否有真正的新消息。
|
||||||
|
* sessionId -> lastMessageTimestamp(秒,与微信 DB 保持一致)
|
||||||
|
*/
|
||||||
|
private lastSeenTimestamp: Map<string, number> = new Map()
|
||||||
|
|
||||||
private started = false
|
private started = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -247,6 +265,48 @@ class InsightService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制立即对最近一个私聊会话触发一次见解(忽略冷却,用于测试)。
|
||||||
|
* 返回触发结果描述,供设置页展示。
|
||||||
|
*/
|
||||||
|
async triggerTest(): Promise<{ success: boolean; message: string }> {
|
||||||
|
console.log('[InsightService] 手动触发测试见解...')
|
||||||
|
const apiBaseUrl = this.config.get('aiInsightApiBaseUrl') as string
|
||||||
|
const apiKey = this.config.get('aiInsightApiKey') as string
|
||||||
|
if (!apiBaseUrl || !apiKey) {
|
||||||
|
return { success: false, message: '请先填写 API 地址和 Key' }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const connectResult = await chatService.connect()
|
||||||
|
if (!connectResult.success) {
|
||||||
|
return { success: false, message: '数据库连接失败,请先在"数据库连接"页完成配置' }
|
||||||
|
}
|
||||||
|
const sessionsResult = await chatService.getSessions()
|
||||||
|
if (!sessionsResult.success || !sessionsResult.sessions || sessionsResult.sessions.length === 0) {
|
||||||
|
return { success: false, message: '未找到任何会话,请确认数据库已正确连接' }
|
||||||
|
}
|
||||||
|
// 找第一个私聊
|
||||||
|
const session = (sessionsResult.sessions as ChatSession[]).find((s) => {
|
||||||
|
const id = s.username?.trim() || ''
|
||||||
|
return id && !id.endsWith('@chatroom') && !id.toLowerCase().includes('placeholder')
|
||||||
|
})
|
||||||
|
if (!session) {
|
||||||
|
return { success: false, message: '未找到任何私聊会话' }
|
||||||
|
}
|
||||||
|
const sessionId = session.username?.trim() || ''
|
||||||
|
const displayName = session.displayName || sessionId
|
||||||
|
console.log(`[InsightService] 测试目标会话:${displayName} (${sessionId})`)
|
||||||
|
await this.generateInsightForSession({
|
||||||
|
sessionId,
|
||||||
|
displayName,
|
||||||
|
triggerReason: 'activity'
|
||||||
|
})
|
||||||
|
return { success: true, message: `已向「${displayName}」发送测试见解,请查看右下角弹窗` }
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, message: `测试失败:${(e as Error).message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取今日触发统计(供设置页展示) */
|
/** 获取今日触发统计(供设置页展示) */
|
||||||
getTodayStats(): { sessionId: string; count: number; times: string[] }[] {
|
getTodayStats(): { sessionId: string; count: number; times: string[] }[] {
|
||||||
this.resetIfNewDay()
|
this.resetIfNewDay()
|
||||||
@@ -323,23 +383,40 @@ class InsightService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async runSilenceScan(): Promise<void> {
|
private async runSilenceScan(): Promise<void> {
|
||||||
if (!this.isEnabled()) return
|
if (!this.isEnabled()) {
|
||||||
if (this.processing) return
|
console.log('[InsightService] 沉默扫描:AI 见解未启用,跳过')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.processing) {
|
||||||
|
console.log('[InsightService] 沉默扫描:正在处理中,跳过本次')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
console.log('[InsightService] 开始沉默联系人扫描...')
|
||||||
try {
|
try {
|
||||||
const silenceDays = (this.config.get('aiInsightSilenceDays') as number) || DEFAULT_SILENCE_DAYS
|
const silenceDays = (this.config.get('aiInsightSilenceDays') as number) || DEFAULT_SILENCE_DAYS
|
||||||
const thresholdMs = silenceDays * 24 * 60 * 60 * 1000
|
const thresholdMs = silenceDays * 24 * 60 * 60 * 1000
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
|
console.log(`[InsightService] 沉默阈值:${silenceDays} 天`)
|
||||||
|
|
||||||
const connectResult = await chatService.connect()
|
const connectResult = await chatService.connect()
|
||||||
if (!connectResult.success) return
|
if (!connectResult.success) {
|
||||||
|
console.log('[InsightService] 数据库连接失败,跳过沉默扫描')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sessionsResult = await chatService.getSessions()
|
const sessionsResult = await chatService.getSessions()
|
||||||
if (!sessionsResult.success || !sessionsResult.sessions) return
|
if (!sessionsResult.success || !sessionsResult.sessions) {
|
||||||
|
console.log('[InsightService] 获取会话列表失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sessions: ChatSession[] = sessionsResult.sessions
|
const sessions: ChatSession[] = sessionsResult.sessions
|
||||||
|
console.log(`[InsightService] 共 ${sessions.length} 个会话,开始过滤...`)
|
||||||
|
|
||||||
|
let silentCount = 0
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
const sessionId = session.username?.trim() || ''
|
const sessionId = session.username?.trim() || ''
|
||||||
if (!sessionId || sessionId.endsWith('@chatroom')) continue // 跳过群聊
|
if (!sessionId || sessionId.endsWith('@chatroom')) continue // 跳过群聊
|
||||||
@@ -353,14 +430,19 @@ class InsightService {
|
|||||||
const silentMs = now - lastTimestamp
|
const silentMs = now - lastTimestamp
|
||||||
if (silentMs < thresholdMs) continue
|
if (silentMs < thresholdMs) continue
|
||||||
|
|
||||||
|
silentCount++
|
||||||
|
const silentDays = Math.floor(silentMs / (24 * 60 * 60 * 1000))
|
||||||
|
console.log(`[InsightService] 发现沉默联系人:${session.displayName || sessionId},已沉默 ${silentDays} 天`)
|
||||||
|
|
||||||
// 沉默时间满足阈值,触发见解
|
// 沉默时间满足阈值,触发见解
|
||||||
await this.generateInsightForSession({
|
await this.generateInsightForSession({
|
||||||
sessionId,
|
sessionId,
|
||||||
displayName: session.displayName || session.username,
|
displayName: session.displayName || session.username,
|
||||||
triggerReason: 'silence',
|
triggerReason: 'silence',
|
||||||
silentDays: Math.floor(silentMs / (24 * 60 * 60 * 1000))
|
silentDays
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
console.log(`[InsightService] 沉默扫描完成,共发现 ${silentCount} 个沉默联系人`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[InsightService] 沉默扫描出错:', e)
|
console.warn('[InsightService] 沉默扫描出错:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -371,36 +453,82 @@ class InsightService {
|
|||||||
// ── 活跃会话分析 ────────────────────────────────────────────────────────────
|
// ── 活跃会话分析 ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 DB 变更防抖后执行,分析最近活跃的会话,
|
* 在 DB 变更防抖后执行,分析最近活跃的会话。
|
||||||
* 判断是否有有趣的上下文值得产出见解。
|
*
|
||||||
|
* 触发条件(必须同时满足):
|
||||||
|
* 1. 会话有真正的新消息(lastTimestamp 比上次见到的更新)
|
||||||
|
* 2. 该会话距上次活跃分析已超过 2 小时冷却期
|
||||||
*/
|
*/
|
||||||
private async analyzeRecentActivity(): Promise<void> {
|
private async analyzeRecentActivity(): Promise<void> {
|
||||||
if (!this.isEnabled()) return
|
if (!this.isEnabled()) return
|
||||||
if (this.processing) return
|
if (this.processing) return
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
console.log('[InsightService] DB 变更防抖触发,开始活跃分析...')
|
||||||
try {
|
try {
|
||||||
const connectResult = await chatService.connect()
|
const connectResult = await chatService.connect()
|
||||||
if (!connectResult.success) return
|
if (!connectResult.success) {
|
||||||
|
console.log('[InsightService] 数据库连接失败,跳过活跃分析')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sessionsResult = await chatService.getSessions()
|
const sessionsResult = await chatService.getSessions()
|
||||||
if (!sessionsResult.success || !sessionsResult.sessions) return
|
if (!sessionsResult.success || !sessionsResult.sessions) {
|
||||||
|
console.log('[InsightService] 获取会话列表失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sessions: ChatSession[] = sessionsResult.sessions
|
const sessions: ChatSession[] = sessionsResult.sessions
|
||||||
// 只取最近有活动的前 5 个会话(排除群聊以降低噪音,可按需调整)
|
const now = Date.now()
|
||||||
const candidates = sessions
|
|
||||||
.filter((s) => {
|
// 筛选私聊会话
|
||||||
const id = s.username?.trim() || ''
|
const privateSessions = sessions.filter((s) => {
|
||||||
return id && !id.endsWith('@chatroom') && !id.toLowerCase().includes('placeholder') && this.isSessionAllowed(id)
|
const id = s.username?.trim() || ''
|
||||||
})
|
return id && !id.endsWith('@chatroom') && !id.toLowerCase().includes('placeholder') && this.isSessionAllowed(id)
|
||||||
.slice(0, 5)
|
})
|
||||||
|
|
||||||
|
console.log(`[InsightService] 筛选到 ${privateSessions.length} 个私聊会话(已过白名单过滤)`)
|
||||||
|
|
||||||
|
let triggeredCount = 0
|
||||||
|
for (const session of privateSessions.slice(0, 10)) {
|
||||||
|
const sessionId = session.username?.trim() || ''
|
||||||
|
if (!sessionId) continue
|
||||||
|
|
||||||
|
const currentTimestamp = session.lastTimestamp || 0
|
||||||
|
const lastSeen = this.lastSeenTimestamp.get(sessionId) ?? 0
|
||||||
|
|
||||||
|
// 检查是否有真正的新消息
|
||||||
|
if (currentTimestamp <= lastSeen) {
|
||||||
|
continue // 没有新消息,跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新已见时间戳
|
||||||
|
this.lastSeenTimestamp.set(sessionId, currentTimestamp)
|
||||||
|
|
||||||
|
// 检查冷却期
|
||||||
|
const lastAnalysis = this.lastActivityAnalysis.get(sessionId) ?? 0
|
||||||
|
const cooldownRemaining = ACTIVITY_COOLDOWN_MS - (now - lastAnalysis)
|
||||||
|
if (cooldownRemaining > 0) {
|
||||||
|
console.log(`[InsightService] ${sessionId} 冷却中,还需 ${Math.ceil(cooldownRemaining / 60000)} 分钟`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[InsightService] ${sessionId} 有新消息且冷却已过,准备生成见解...`)
|
||||||
|
this.lastActivityAnalysis.set(sessionId, now)
|
||||||
|
|
||||||
for (const session of candidates) {
|
|
||||||
await this.generateInsightForSession({
|
await this.generateInsightForSession({
|
||||||
sessionId: session.username?.trim() || '',
|
sessionId,
|
||||||
displayName: session.displayName || session.username,
|
displayName: session.displayName || session.username,
|
||||||
triggerReason: 'activity'
|
triggerReason: 'activity'
|
||||||
})
|
})
|
||||||
|
triggeredCount++
|
||||||
|
|
||||||
|
// 每次最多处理 1 个会话,避免单次防抖后批量调用
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggeredCount === 0) {
|
||||||
|
console.log('[InsightService] 活跃分析完成,无会话触发见解')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[InsightService] 活跃分析出错:', e)
|
console.warn('[InsightService] 活跃分析出错:', e)
|
||||||
@@ -425,9 +553,14 @@ class InsightService {
|
|||||||
const model = (this.config.get('aiInsightApiModel') as string) || 'gpt-4o-mini'
|
const model = (this.config.get('aiInsightApiModel') as string) || 'gpt-4o-mini'
|
||||||
const allowContext = this.config.get('aiInsightAllowContext') as boolean
|
const allowContext = this.config.get('aiInsightAllowContext') as boolean
|
||||||
|
|
||||||
if (!apiBaseUrl || !apiKey) return
|
console.log(`[InsightService] generateInsightForSession: sessionId=${sessionId}, reason=${triggerReason}, apiBaseUrl=${apiBaseUrl ? '已配置' : '未配置'}, apiKey=${apiKey ? '已配置' : '未配置'}`)
|
||||||
|
|
||||||
// ── 构建 prompt ──────────────────────────────────────────────────────────
|
if (!apiBaseUrl || !apiKey) {
|
||||||
|
console.warn('[InsightService] API 地址或 Key 未配置,跳过见解生成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 构建 prompt ─────────────<E29480><E29480><EFBFBD>────────────────────────────────────────────
|
||||||
|
|
||||||
// 今日触发统计(让模型具备时间与克制感)
|
// 今日触发统计(让模型具备时间与克制感)
|
||||||
const sessionTriggerTimes = this.recordTrigger(sessionId)
|
const sessionTriggerTimes = this.recordTrigger(sessionId)
|
||||||
@@ -475,6 +608,9 @@ class InsightService {
|
|||||||
|
|
||||||
请给出你的见解(≤80字)或回复 SKIP 跳过:`
|
请给出你的见解(≤80字)或回复 SKIP 跳过:`
|
||||||
|
|
||||||
|
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
|
||||||
|
console.log(`[InsightService] 准备调用 API: ${endpoint},模型: ${model}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await callApi(
|
const result = await callApi(
|
||||||
apiBaseUrl,
|
apiBaseUrl,
|
||||||
@@ -486,6 +622,8 @@ class InsightService {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log(`[InsightService] API 返回原文: ${result.slice(0, 100)}`)
|
||||||
|
|
||||||
// 模型主动选择跳过
|
// 模型主动选择跳过
|
||||||
if (result.trim().toUpperCase() === 'SKIP' || result.trim().startsWith('SKIP')) {
|
if (result.trim().toUpperCase() === 'SKIP' || result.trim().startsWith('SKIP')) {
|
||||||
console.log(`[InsightService] 模型选择跳过 ${sessionId}`)
|
console.log(`[InsightService] 模型选择跳过 ${sessionId}`)
|
||||||
@@ -495,11 +633,12 @@ class InsightService {
|
|||||||
const insight = result.slice(0, 120) // 防止超长截断
|
const insight = result.slice(0, 120) // 防止超长截断
|
||||||
|
|
||||||
// 通过现有 showNotification 推送弹窗
|
// 通过现有 showNotification 推送弹窗
|
||||||
|
console.log(`[InsightService] 准备推送通知: ${insight}`)
|
||||||
await showNotification({
|
await showNotification({
|
||||||
sessionId,
|
sessionId,
|
||||||
sourceName: `见解 · ${displayName}`,
|
sourceName: `见解 · ${displayName}`,
|
||||||
content: insight,
|
content: insight,
|
||||||
isInsight: true // 可用于通知窗口做差异化展示
|
isInsight: true
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[InsightService] 已为 ${sessionId} 推送见解`)
|
console.log(`[InsightService] 已为 ${sessionId} 推送见解`)
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const [isTestingInsight, setIsTestingInsight] = useState(false)
|
const [isTestingInsight, setIsTestingInsight] = useState(false)
|
||||||
const [insightTestResult, setInsightTestResult] = useState<{ success: boolean; message: string } | null>(null)
|
const [insightTestResult, setInsightTestResult] = useState<{ success: boolean; message: string } | null>(null)
|
||||||
const [showInsightApiKey, setShowInsightApiKey] = useState(false)
|
const [showInsightApiKey, setShowInsightApiKey] = useState(false)
|
||||||
|
const [isTriggeringInsightTest, setIsTriggeringInsightTest] = useState(false)
|
||||||
|
const [insightTriggerResult, setInsightTriggerResult] = useState<{ success: boolean; message: string } | null>(null)
|
||||||
const [aiInsightWhitelistEnabled, setAiInsightWhitelistEnabled] = useState(false)
|
const [aiInsightWhitelistEnabled, setAiInsightWhitelistEnabled] = useState(false)
|
||||||
const [aiInsightWhitelist, setAiInsightWhitelist] = useState<Set<string>>(new Set())
|
const [aiInsightWhitelist, setAiInsightWhitelist] = useState<Set<string>>(new Set())
|
||||||
const [insightWhitelistSearch, setInsightWhitelistSearch] = useState('')
|
const [insightWhitelistSearch, setInsightWhitelistSearch] = useState('')
|
||||||
@@ -2609,26 +2611,65 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 测试连接 */}
|
{/* 测试连接 + 触发测试 */}
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
|
<label>调试工具</label>
|
||||||
<button
|
<span className="form-hint">
|
||||||
className="btn btn-secondary"
|
先用"测试 API 连接"确认 Key 和 URL 填写正确,再用"立即触发测试见解"验证完整链路(数据库→API→弹窗)。触发后请留意右下角通知弹窗。
|
||||||
onClick={handleTestInsightConnection}
|
</span>
|
||||||
disabled={isTestingInsight || !aiInsightApiBaseUrl || !aiInsightApiKey}
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginTop: '10px' }}>
|
||||||
>
|
{/* 测试 API 连接 */}
|
||||||
{isTestingInsight ? (
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
<><Loader2 size={14} style={{ marginRight: 4, animation: 'spin 1s linear infinite' }} /> 测试中...</>
|
<button
|
||||||
) : (
|
className="btn btn-secondary"
|
||||||
<>测试 API 连接</>
|
onClick={handleTestInsightConnection}
|
||||||
|
disabled={isTestingInsight || !aiInsightApiBaseUrl || !aiInsightApiKey}
|
||||||
|
>
|
||||||
|
{isTestingInsight ? (
|
||||||
|
<><Loader2 size={14} style={{ marginRight: 4, animation: 'spin 1s linear infinite' }} />测试中...</>
|
||||||
|
) : (
|
||||||
|
<>测试 API 连接</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{insightTestResult && (
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: insightTestResult.success ? 'var(--color-success, #22c55e)' : 'var(--color-danger, #ef4444)' }}>
|
||||||
|
{insightTestResult.success ? <CheckCircle2 size={14} /> : <XCircle size={14} />}
|
||||||
|
{insightTestResult.message}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</div>
|
||||||
{insightTestResult && (
|
{/* 触发测试见解 */}
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: insightTestResult.success ? 'var(--color-success, #22c55e)' : 'var(--color-danger, #ef4444)' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
{insightTestResult.success ? <CheckCircle2 size={14} /> : <XCircle size={14} />}
|
<button
|
||||||
{insightTestResult.message}
|
className="btn btn-secondary"
|
||||||
</span>
|
onClick={async () => {
|
||||||
)}
|
setIsTriggeringInsightTest(true)
|
||||||
|
setInsightTriggerResult(null)
|
||||||
|
try {
|
||||||
|
const result = await (window.electronAPI as any).insight.triggerTest()
|
||||||
|
setInsightTriggerResult(result)
|
||||||
|
} catch (e: any) {
|
||||||
|
setInsightTriggerResult({ success: false, message: `调用失败:${e?.message || String(e)}` })
|
||||||
|
} finally {
|
||||||
|
setIsTriggeringInsightTest(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isTriggeringInsightTest || !aiInsightEnabled || !aiInsightApiBaseUrl || !aiInsightApiKey}
|
||||||
|
title={!aiInsightEnabled ? '请先开启 AI 见解总开关' : ''}
|
||||||
|
>
|
||||||
|
{isTriggeringInsightTest ? (
|
||||||
|
<><Loader2 size={14} style={{ marginRight: 4, animation: 'spin 1s linear infinite' }} />触发中...</>
|
||||||
|
) : (
|
||||||
|
<>立即触发测试见解</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{insightTriggerResult && (
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: insightTriggerResult.success ? 'var(--color-success, #22c55e)' : 'var(--color-danger, #ef4444)' }}>
|
||||||
|
{insightTriggerResult.success ? <CheckCircle2 size={14} /> : <XCircle size={14} />}
|
||||||
|
{insightTriggerResult.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2879,7 +2920,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
<p className="api-desc" style={{ lineHeight: 1.7 }}>
|
<p className="api-desc" style={{ lineHeight: 1.7 }}>
|
||||||
<strong>触发方式一:活跃会话分析</strong> — 每当微信数据库变化(即你收到新消息)时,经过 500ms 防抖后,对最近活跃的私聊会话进行分析。<br />
|
<strong>触发方式一:活跃会话分析</strong> — 每当微信数据库变化(即你收到新消息)时,经过 500ms 防抖后,对最近活跃的私聊会话进行分析。<br />
|
||||||
<strong>触发方式二:沉默扫描</strong> — 每 4 小时独立扫描一次,对超过阈值天数无消息的联系人发出提醒。<br />
|
<strong>触发方式二:沉默扫描</strong> — 每 4 小时独立扫描一次,对超过阈值天数无消息的联系人发出提醒。<br />
|
||||||
<strong>时间观念</strong> — 每次调用时,AI 会收到今天已向该联系人和全局发出过多少次见解,由 AI 自行决定是否需要克制。<br />
|
<strong>时间观念</strong> — 每次调用时<EFBFBD><EFBFBD>AI 会收到今天已向该联系人和全局发出过多少次见解,由 AI 自行决定是否需要克制。<br />
|
||||||
<strong>隐私</strong> — 所有分析请求均直接从你的电脑发往你填写的 API 地址,不经过任何 WeFlow 服务器。
|
<strong>隐私</strong> — 所有分析请求均直接从你的电脑发往你填写的 API 地址,不经过任何 WeFlow 服务器。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -3123,7 +3164,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
try {
|
try {
|
||||||
const verifyResult = await window.electronAPI.auth.hello('请验证您的身份以开启 Windows Hello')
|
const verifyResult = await window.electronAPI.auth.hello('请验证您的身份以开启 Windows Hello')
|
||||||
if (!verifyResult.success) {
|
if (!verifyResult.success) {
|
||||||
showMessage(verifyResult.error || 'Windows Hello 验证失败', false)
|
showMessage(verifyResult.error || 'Windows Hello <EFBFBD><EFBFBD>证失败', false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user