Merge pull request #20 from Jasonzhu1207/feature/ai-insight-debug-log-toggle

feat: add AI insight debug log export toggle
This commit is contained in:
Jason
2026-04-12 15:49:36 +08:00
committed by GitHub
4 changed files with 194 additions and 42 deletions

View File

@@ -102,6 +102,8 @@ interface ConfigSchema {
// AI 足迹 // AI 足迹
aiFootprintEnabled: boolean aiFootprintEnabled: boolean
aiFootprintSystemPrompt: string aiFootprintSystemPrompt: string
/** 是否将 AI 见解调试日志输出到桌面 */
aiInsightDebugLogEnabled: boolean
} }
// 需要 safeStorage 加密的字段(普通模式) // 需要 safeStorage 加密的字段(普通模式)
@@ -204,7 +206,8 @@ export class ConfigService {
aiInsightTelegramToken: '', aiInsightTelegramToken: '',
aiInsightTelegramChatIds: '', aiInsightTelegramChatIds: '',
aiFootprintEnabled: false, aiFootprintEnabled: false,
aiFootprintSystemPrompt: '' aiFootprintSystemPrompt: '',
aiInsightDebugLogEnabled: false
} }
const storeOptions: any = { const storeOptions: any = {

View File

@@ -15,8 +15,10 @@
import https from 'https' import https from 'https'
import http from 'http' import http from 'http'
import fs from 'fs'
import path from 'path'
import { URL } from 'url' import { URL } from 'url'
import { Notification } from 'electron' import { app, Notification } from 'electron'
import { ConfigService } from './config' import { ConfigService } from './config'
import { chatService, ChatSession, Message } from './chatService' import { chatService, ChatSession, Message } from './chatService'
@@ -33,6 +35,8 @@ const SILENCE_SCAN_INITIAL_DELAY_MS = 3 * 60 * 1000
/** 单次 API 请求超时(毫秒) */ /** 单次 API 请求超时(毫秒) */
const API_TIMEOUT_MS = 45_000 const API_TIMEOUT_MS = 45_000
const API_MAX_TOKENS = 200
const API_TEMPERATURE = 0.7
/** 沉默天数阈值默认值 */ /** 沉默天数阈值默认值 */
const DEFAULT_SILENCE_DAYS = 3 const DEFAULT_SILENCE_DAYS = 3
@@ -62,15 +66,74 @@ interface SharedAiModelConfig {
// ─── 日志 ───────────────────────────────────────────────────────────────────── // ─── 日志 ─────────────────────────────────────────────────────────────────────
type InsightLogLevel = 'INFO' | 'WARN' | 'ERROR'
let debugLogWriteQueue: Promise<void> = Promise.resolve()
function formatDebugTimestamp(date: Date = new Date()): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
function getInsightDebugLogFilePath(date: Date = new Date()): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return path.join(app.getPath('desktop'), `weflow-ai-insight-debug-${year}-${month}-${day}.log`)
}
function isInsightDebugLogEnabled(): boolean {
try {
return ConfigService.getInstance().get('aiInsightDebugLogEnabled') === true
} catch {
return false
}
}
function appendInsightDebugText(text: string): void {
if (!isInsightDebugLogEnabled()) return
let logFilePath = ''
try {
logFilePath = getInsightDebugLogFilePath()
} catch {
return
}
debugLogWriteQueue = debugLogWriteQueue
.then(() => fs.promises.appendFile(logFilePath, text, 'utf8'))
.catch(() => undefined)
}
function insightDebugLine(level: InsightLogLevel, message: string): void {
appendInsightDebugText(`[${formatDebugTimestamp()}] [${level}] ${message}\n`)
}
function insightDebugSection(level: InsightLogLevel, title: string, payload: unknown): void {
const content = typeof payload === 'string'
? payload
: JSON.stringify(payload, null, 2)
appendInsightDebugText(
`\n========== [${formatDebugTimestamp()}] [${level}] ${title} ==========\n${content}\n========== END ==========\n`
)
}
/** /**
* 仅输出到 console不落盘到文件。 * 仅输出到 console不落盘到文件。
*/ */
function insightLog(level: 'INFO' | 'WARN' | 'ERROR', message: string): void { function insightLog(level: InsightLogLevel, message: string): void {
if (level === 'ERROR' || level === 'WARN') { if (level === 'ERROR' || level === 'WARN') {
console.warn(`[InsightService] ${message}`) console.warn(`[InsightService] ${message}`)
} else { } else {
console.log(`[InsightService] ${message}`) console.log(`[InsightService] ${message}`)
} }
insightDebugLine(level, message)
} }
// ─── 工具函数 ───────────────────────────────────────────────────────────────── // ─── 工具函数 ─────────────────────────────────────────────────────────────────
@@ -127,8 +190,8 @@ function callApi(
const body = JSON.stringify({ const body = JSON.stringify({
model, model,
messages, messages,
max_tokens: 200, max_tokens: API_MAX_TOKENS,
temperature: 0.7, temperature: API_TEMPERATURE,
stream: false stream: false
}) })
@@ -336,15 +399,34 @@ class InsightService {
} }
try { try {
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
const requestMessages = [{ role: 'user', content: '请回复"连接成功"四个字。' }]
insightDebugSection('INFO', 'AI 测试连接请求', {
endpoint,
model,
request: {
model,
messages: requestMessages,
max_tokens: API_MAX_TOKENS,
temperature: API_TEMPERATURE,
stream: false
}
})
const result = await callApi( const result = await callApi(
apiBaseUrl, apiBaseUrl,
apiKey, apiKey,
model, model,
[{ role: 'user', content: '请回复"连接成功"四个字。' }], requestMessages,
15_000 15_000
) )
insightDebugSection('INFO', 'AI 测试连接输出原文', result)
return { success: true, message: `连接成功,模型回复:${result.slice(0, 50)}` } return { success: true, message: `连接成功,模型回复:${result.slice(0, 50)}` }
} catch (e) { } catch (e) {
insightDebugSection('ERROR', 'AI 测试连接失败', {
error: (e as Error).message,
stack: (e as Error).stack ?? null
})
return { success: false, message: `连接失败:${(e as Error).message}` } return { success: false, message: `连接失败:${(e as Error).message}` }
} }
} }
@@ -884,20 +966,40 @@ ${topMentionText}
请给出你的见解≤80字` 请给出你的见解≤80字`
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions') const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
const requestMessages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]
insightLog('INFO', `准备调用 API: ${endpoint},模型: ${model}`) insightLog('INFO', `准备调用 API: ${endpoint},模型: ${model}`)
insightDebugSection('INFO', `AI 请求 ${displayName} (${sessionId})`, {
sessionId,
displayName,
triggerReason,
silentDays: silentDays ?? null,
endpoint,
model,
allowContext,
contextCount,
request: {
model,
messages: requestMessages,
max_tokens: API_MAX_TOKENS,
temperature: API_TEMPERATURE,
stream: false
}
})
try { try {
const result = await callApi( const result = await callApi(
apiBaseUrl, apiBaseUrl,
apiKey, apiKey,
model, model,
[ requestMessages
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]
) )
insightLog('INFO', `API 返回原文: ${result.slice(0, 150)}`) insightLog('INFO', `API 返回原文: ${result.slice(0, 150)}`)
insightDebugSection('INFO', `AI 输出原文 ${displayName} (${sessionId})`, result)
// 模型主动选择跳过 // 模型主动选择跳过
if (result.trim().toUpperCase() === 'SKIP' || result.trim().startsWith('SKIP')) { if (result.trim().toUpperCase() === 'SKIP' || result.trim().startsWith('SKIP')) {
@@ -939,6 +1041,13 @@ ${topMentionText}
insightLog('INFO', `已为 ${displayName} 推送见解`) insightLog('INFO', `已为 ${displayName} 推送见解`)
} catch (e) { } catch (e) {
insightDebugSection('ERROR', `AI 请求失败 ${displayName} (${sessionId})`, {
sessionId,
displayName,
triggerReason,
error: (e as Error).message,
stack: (e as Error).stack ?? null
})
insightLog('ERROR', `API 调用失败 (${displayName}): ${(e as Error).message}`) insightLog('ERROR', `API 调用失败 (${displayName}): ${(e as Error).message}`)
} }
} }

View File

@@ -286,6 +286,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const [aiInsightTelegramChatIds, setAiInsightTelegramChatIds] = useState('') const [aiInsightTelegramChatIds, setAiInsightTelegramChatIds] = useState('')
const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false) const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false)
const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('') const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('')
const [aiInsightDebugLogEnabled, setAiInsightDebugLogEnabled] = useState(false)
// 检查 Hello 可用性 // 检查 Hello 可用性
useEffect(() => { useEffect(() => {
@@ -528,6 +529,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const savedAiInsightTelegramChatIds = await configService.getAiInsightTelegramChatIds() const savedAiInsightTelegramChatIds = await configService.getAiInsightTelegramChatIds()
const savedAiFootprintEnabled = await configService.getAiFootprintEnabled() const savedAiFootprintEnabled = await configService.getAiFootprintEnabled()
const savedAiFootprintSystemPrompt = await configService.getAiFootprintSystemPrompt() const savedAiFootprintSystemPrompt = await configService.getAiFootprintSystemPrompt()
const savedAiInsightDebugLogEnabled = await configService.getAiInsightDebugLogEnabled()
setAiInsightEnabled(savedAiInsightEnabled) setAiInsightEnabled(savedAiInsightEnabled)
setAiModelApiBaseUrl(savedAiModelApiBaseUrl) setAiModelApiBaseUrl(savedAiModelApiBaseUrl)
setAiModelApiKey(savedAiModelApiKey) setAiModelApiKey(savedAiModelApiKey)
@@ -545,6 +548,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
setAiInsightTelegramChatIds(savedAiInsightTelegramChatIds) setAiInsightTelegramChatIds(savedAiInsightTelegramChatIds)
setAiFootprintEnabled(savedAiFootprintEnabled) setAiFootprintEnabled(savedAiFootprintEnabled)
setAiFootprintSystemPrompt(savedAiFootprintSystemPrompt) setAiFootprintSystemPrompt(savedAiFootprintSystemPrompt)
setAiInsightDebugLogEnabled(savedAiInsightDebugLogEnabled)
} catch (e: any) { } catch (e: any) {
console.error('加载配置失败:', e) console.error('加载配置失败:', e)
@@ -2722,7 +2726,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
setIsTestingInsight(true) setIsTestingInsight(true)
setInsightTestResult(null) setInsightTestResult(null)
try { try {
const result = await (window.electronAPI as any).insight.testConnection() const result = await window.electronAPI.insight.testConnection()
setInsightTestResult(result) setInsightTestResult(result)
} catch (e: any) { } catch (e: any) {
setInsightTestResult({ success: false, message: `调用失败:${e?.message || String(e)}` }) setInsightTestResult({ success: false, message: `调用失败:${e?.message || String(e)}` })
@@ -2883,7 +2887,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
setIsTriggeringInsightTest(true) setIsTriggeringInsightTest(true)
setInsightTriggerResult(null) setInsightTriggerResult(null)
try { try {
const result = await (window.electronAPI as any).insight.triggerTest() const result = await window.electronAPI.insight.triggerTest()
setInsightTriggerResult(result) setInsightTriggerResult(result)
} catch (e: any) { } catch (e: any) {
setInsightTriggerResult({ success: false, message: `调用失败:${e?.message || String(e)}` }) setInsightTriggerResult({ success: false, message: `调用失败:${e?.message || String(e)}` })
@@ -3340,6 +3344,32 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</div> </div>
</div> </div>
</div> </div>
<div className="divider" />
<div className="form-group">
<label></label>
<span className="form-hint">
AI <code>weflow-ai-insight-debug-YYYY-MM-DD.log</code>
AI API Key
</span>
<div className="log-toggle-line">
<span className="log-status">{aiInsightDebugLogEnabled ? '已开启' : '已关闭'}</span>
<label className="switch">
<input
type="checkbox"
checked={aiInsightDebugLogEnabled}
onChange={async (e) => {
const val = e.target.checked
setAiInsightDebugLogEnabled(val)
await configService.setAiInsightDebugLogEnabled(val)
showMessage(val ? '已开启 AI 见解调试日志,后续日志将写入桌面' : '已关闭 AI 见解调试日志', true)
}}
/>
<span className="switch-slider" />
</label>
</div>
</div>
</div> </div>
) )

View File

@@ -106,7 +106,8 @@ export const CONFIG_KEYS = {
// AI 足迹 // AI 足迹
AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled', AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled',
AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt' AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt',
AI_INSIGHT_DEBUG_LOG_ENABLED: 'aiInsightDebugLogEnabled'
} as const } as const
export interface WxidConfig { export interface WxidConfig {
@@ -1803,3 +1804,12 @@ export async function getAiFootprintSystemPrompt(): Promise<string> {
export async function setAiFootprintSystemPrompt(prompt: string): Promise<void> { export async function setAiFootprintSystemPrompt(prompt: string): Promise<void> {
await config.set(CONFIG_KEYS.AI_FOOTPRINT_SYSTEM_PROMPT, prompt) await config.set(CONFIG_KEYS.AI_FOOTPRINT_SYSTEM_PROMPT, prompt)
} }
export async function getAiInsightDebugLogEnabled(): Promise<boolean> {
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_DEBUG_LOG_ENABLED)
return value === true
}
export async function setAiInsightDebugLogEnabled(enabled: boolean): Promise<void> {
await config.set(CONFIG_KEYS.AI_INSIGHT_DEBUG_LOG_ENABLED, enabled)
}