mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-12 15:08:36 +00:00
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:
@@ -102,6 +102,8 @@ interface ConfigSchema {
|
||||
// AI 足迹
|
||||
aiFootprintEnabled: boolean
|
||||
aiFootprintSystemPrompt: string
|
||||
/** 是否将 AI 见解调试日志输出到桌面 */
|
||||
aiInsightDebugLogEnabled: boolean
|
||||
}
|
||||
|
||||
// 需要 safeStorage 加密的字段(普通模式)
|
||||
@@ -204,7 +206,8 @@ export class ConfigService {
|
||||
aiInsightTelegramToken: '',
|
||||
aiInsightTelegramChatIds: '',
|
||||
aiFootprintEnabled: false,
|
||||
aiFootprintSystemPrompt: ''
|
||||
aiFootprintSystemPrompt: '',
|
||||
aiInsightDebugLogEnabled: false
|
||||
}
|
||||
|
||||
const storeOptions: any = {
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
|
||||
import https from 'https'
|
||||
import http from 'http'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { URL } from 'url'
|
||||
import { Notification } from 'electron'
|
||||
import { app, Notification } from 'electron'
|
||||
import { ConfigService } from './config'
|
||||
import { chatService, ChatSession, Message } from './chatService'
|
||||
|
||||
@@ -33,6 +35,8 @@ const SILENCE_SCAN_INITIAL_DELAY_MS = 3 * 60 * 1000
|
||||
|
||||
/** 单次 API 请求超时(毫秒) */
|
||||
const API_TIMEOUT_MS = 45_000
|
||||
const API_MAX_TOKENS = 200
|
||||
const API_TEMPERATURE = 0.7
|
||||
|
||||
/** 沉默天数阈值默认值 */
|
||||
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,不落盘到文件。
|
||||
*/
|
||||
function insightLog(level: 'INFO' | 'WARN' | 'ERROR', message: string): void {
|
||||
function insightLog(level: InsightLogLevel, message: string): void {
|
||||
if (level === 'ERROR' || level === 'WARN') {
|
||||
console.warn(`[InsightService] ${message}`)
|
||||
} else {
|
||||
console.log(`[InsightService] ${message}`)
|
||||
}
|
||||
insightDebugLine(level, message)
|
||||
}
|
||||
|
||||
// ─── 工具函数 ─────────────────────────────────────────────────────────────────
|
||||
@@ -127,8 +190,8 @@ function callApi(
|
||||
const body = JSON.stringify({
|
||||
model,
|
||||
messages,
|
||||
max_tokens: 200,
|
||||
temperature: 0.7,
|
||||
max_tokens: API_MAX_TOKENS,
|
||||
temperature: API_TEMPERATURE,
|
||||
stream: false
|
||||
})
|
||||
|
||||
@@ -336,15 +399,34 @@ class InsightService {
|
||||
}
|
||||
|
||||
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(
|
||||
apiBaseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
[{ role: 'user', content: '请回复"连接成功"四个字。' }],
|
||||
requestMessages,
|
||||
15_000
|
||||
)
|
||||
insightDebugSection('INFO', 'AI 测试连接输出原文', result)
|
||||
return { success: true, message: `连接成功,模型回复:${result.slice(0, 50)}` }
|
||||
} catch (e) {
|
||||
insightDebugSection('ERROR', 'AI 测试连接失败', {
|
||||
error: (e as Error).message,
|
||||
stack: (e as Error).stack ?? null
|
||||
})
|
||||
return { success: false, message: `连接失败:${(e as Error).message}` }
|
||||
}
|
||||
}
|
||||
@@ -884,20 +966,40 @@ ${topMentionText}
|
||||
请给出你的见解(≤80字):`
|
||||
|
||||
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
|
||||
const requestMessages = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt }
|
||||
]
|
||||
|
||||
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 {
|
||||
const result = await callApi(
|
||||
apiBaseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
[
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt }
|
||||
]
|
||||
requestMessages
|
||||
)
|
||||
|
||||
insightLog('INFO', `API 返回原文: ${result.slice(0, 150)}`)
|
||||
insightDebugSection('INFO', `AI 输出原文 ${displayName} (${sessionId})`, result)
|
||||
|
||||
// 模型主动选择跳过
|
||||
if (result.trim().toUpperCase() === 'SKIP' || result.trim().startsWith('SKIP')) {
|
||||
@@ -939,6 +1041,13 @@ ${topMentionText}
|
||||
|
||||
insightLog('INFO', `已为 ${displayName} 推送见解`)
|
||||
} 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}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [aiInsightTelegramChatIds, setAiInsightTelegramChatIds] = useState('')
|
||||
const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false)
|
||||
const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('')
|
||||
const [aiInsightDebugLogEnabled, setAiInsightDebugLogEnabled] = useState(false)
|
||||
|
||||
// 检查 Hello 可用性
|
||||
useEffect(() => {
|
||||
@@ -528,6 +529,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const savedAiInsightTelegramChatIds = await configService.getAiInsightTelegramChatIds()
|
||||
const savedAiFootprintEnabled = await configService.getAiFootprintEnabled()
|
||||
const savedAiFootprintSystemPrompt = await configService.getAiFootprintSystemPrompt()
|
||||
const savedAiInsightDebugLogEnabled = await configService.getAiInsightDebugLogEnabled()
|
||||
|
||||
setAiInsightEnabled(savedAiInsightEnabled)
|
||||
setAiModelApiBaseUrl(savedAiModelApiBaseUrl)
|
||||
setAiModelApiKey(savedAiModelApiKey)
|
||||
@@ -545,6 +548,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setAiInsightTelegramChatIds(savedAiInsightTelegramChatIds)
|
||||
setAiFootprintEnabled(savedAiFootprintEnabled)
|
||||
setAiFootprintSystemPrompt(savedAiFootprintSystemPrompt)
|
||||
setAiInsightDebugLogEnabled(savedAiInsightDebugLogEnabled)
|
||||
|
||||
} catch (e: any) {
|
||||
console.error('加载配置失败:', e)
|
||||
@@ -2722,7 +2726,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setIsTestingInsight(true)
|
||||
setInsightTestResult(null)
|
||||
try {
|
||||
const result = await (window.electronAPI as any).insight.testConnection()
|
||||
const result = await window.electronAPI.insight.testConnection()
|
||||
setInsightTestResult(result)
|
||||
} catch (e: any) {
|
||||
setInsightTestResult({ success: false, message: `调用失败:${e?.message || String(e)}` })
|
||||
@@ -2883,7 +2887,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setIsTriggeringInsightTest(true)
|
||||
setInsightTriggerResult(null)
|
||||
try {
|
||||
const result = await (window.electronAPI as any).insight.triggerTest()
|
||||
const result = await window.electronAPI.insight.triggerTest()
|
||||
setInsightTriggerResult(result)
|
||||
} catch (e: any) {
|
||||
setInsightTriggerResult({ success: false, message: `调用失败:${e?.message || String(e)}` })
|
||||
@@ -3340,6 +3344,32 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
</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>
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,8 @@ export const CONFIG_KEYS = {
|
||||
|
||||
// AI 足迹
|
||||
AI_FOOTPRINT_ENABLED: 'aiFootprintEnabled',
|
||||
AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt'
|
||||
AI_FOOTPRINT_SYSTEM_PROMPT: 'aiFootprintSystemPrompt',
|
||||
AI_INSIGHT_DEBUG_LOG_ENABLED: 'aiInsightDebugLogEnabled'
|
||||
} as const
|
||||
|
||||
export interface WxidConfig {
|
||||
@@ -1803,3 +1804,12 @@ export async function getAiFootprintSystemPrompt(): Promise<string> {
|
||||
export async function setAiFootprintSystemPrompt(prompt: string): Promise<void> {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user