fix(insight): trim prompt noise and smooth settings animation

This commit is contained in:
Jason
2026-04-28 13:30:17 +08:00
parent c596d24083
commit 9f9ad337ab
3 changed files with 26 additions and 59 deletions

View File

@@ -10,7 +10,7 @@
* 设计原则: * 设计原则:
* - 不引入任何额外 npm 依赖,使用 Node 原生 https 模块调用 OpenAI 兼容 API * - 不引入任何额外 npm 依赖,使用 Node 原生 https 模块调用 OpenAI 兼容 API
* - 所有失败静默处理,不影响主流程 * - 所有失败静默处理,不影响主流程
* - 当日触发记录sessionId + 时间列表)随 prompt 一起发送,让模型自行判断是否克制 * - 触发频率、冷却与名单过滤均在本地完成,不把调度统计塞进模型 prompt
*/ */
import https from 'https' import https from 'https'
@@ -449,7 +449,7 @@ class InsightService {
try { try {
const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions') const endpoint = buildApiUrl(apiBaseUrl, '/chat/completions')
const requestMessages = [{ role: 'user', content: appendPromptCurrentTime('请回复"连接成功"四个字。') }] const requestMessages = [{ role: 'user', content: '请回复"连接成功"四个字。' }]
insightDebugSection( insightDebugSection(
'INFO', 'INFO',
'AI 测试连接请求', 'AI 测试连接请求',
@@ -827,26 +827,13 @@ ${topMentionText}
} }
/** /**
* 记录触发并返回该会话今日所有触发时间(用于组装 prompt * 记录成功推送的见解,用于设置页展示今日触发统计
*/ */
private recordTrigger(sessionId: string): string[] { private recordTrigger(sessionId: string): void {
this.resetIfNewDay() this.resetIfNewDay()
const existing = this.todayTriggers.get(sessionId) ?? { timestamps: [] } const existing = this.todayTriggers.get(sessionId) ?? { timestamps: [] }
existing.timestamps.push(Date.now()) existing.timestamps.push(Date.now())
this.todayTriggers.set(sessionId, existing) this.todayTriggers.set(sessionId, existing)
return existing.timestamps.map(formatTimestamp)
}
/**
* 获取今日全局已触发次数(所有会话合计),用于 prompt 中告知模型全局上下文。
*/
private getTodayTotalTriggerCount(): number {
this.resetIfNewDay()
let total = 0
for (const record of this.todayTriggers.values()) {
total += record.timestamps.length
}
return total
} }
private formatWeiboTimestamp(raw: string): string { private formatWeiboTimestamp(raw: string): string {
@@ -905,7 +892,7 @@ ${topMentionText}
if (lines.length === 0) return '' if (lines.length === 0) return ''
insightLog('INFO', `已加载 ${lines.length} 条朋友圈内容 (sessionId=${sessionId})`) insightLog('INFO', `已加载 ${lines.length} 条朋友圈内容 (sessionId=${sessionId})`)
return `以下是该联系人的朋友圈内容(仅人类可读原文,最近 ${lines.length} 条):\n${lines.join('\n')}` return `近期朋友圈内容(最近 ${lines.length} 条):\n${lines.join('\n')}`
} catch (error) { } catch (error) {
insightLog('WARN', `拉取朋友圈内容失败 (sessionId=${sessionId}): ${(error as Error).message}`) insightLog('WARN', `拉取朋友圈内容失败 (sessionId=${sessionId}): ${(error as Error).message}`)
return '' return ''
@@ -917,7 +904,6 @@ ${topMentionText}
if (!allowSocialContext) return '' if (!allowSocialContext) return ''
const rawCookie = String(this.config.get('aiInsightWeiboCookie') || '').trim() const rawCookie = String(this.config.get('aiInsightWeiboCookie') || '').trim()
const hasCookie = rawCookie.length > 0
const bindings = const bindings =
(this.config.get('aiInsightWeiboBindings') as Record<string, { uid?: string; screenName?: string }> | undefined) || {} (this.config.get('aiInsightWeiboBindings') as Record<string, { uid?: string; screenName?: string }> | undefined) || {}
@@ -938,10 +924,7 @@ ${topMentionText}
return `[微博 ${time}] ${text}` return `[微博 ${time}] ${text}`
}) })
insightLog('INFO', `已加载 ${lines.length} 条微博公开内容 (uid=${uid})`) insightLog('INFO', `已加载 ${lines.length} 条微博公开内容 (uid=${uid})`)
const riskHint = hasCookie return `近期公开社交平台内容(来源:微博,最近 ${lines.length} 条):\n${lines.join('\n')}`
? ''
: '\n提示未配置微博 Cookie使用移动端公开接口抓取可能因平台风控导致获取失败或内容较少。'
return `近期公开社交平台内容(来源:微博,最近 ${lines.length} 条):\n${lines.join('\n')}${riskHint}`
} catch (error) { } catch (error) {
insightLog('WARN', `拉取微博公开内容失败 (uid=${uid}): ${(error as Error).message}`) insightLog('WARN', `拉取微博公开内容失败 (uid=${uid}): ${(error as Error).message}`)
return '' return ''
@@ -1177,10 +1160,6 @@ ${topMentionText}
// ── 构建 prompt ──────────────────────────────────────────────────────────── // ── 构建 prompt ────────────────────────────────────────────────────────────
// 今日触发统计(让模型具备时间与克制感)
const sessionTriggerTimes = this.recordTrigger(sessionId)
const totalTodayTriggers = this.getTodayTotalTriggerCount()
let contextSection = '' let contextSection = ''
if (allowContext) { if (allowContext) {
try { try {
@@ -1211,24 +1190,10 @@ ${topMentionText}
const customPrompt = (this.config.get('aiInsightSystemPrompt') as string) || '' const customPrompt = (this.config.get('aiInsightSystemPrompt') as string) || ''
const systemPrompt = customPrompt.trim() || DEFAULT_SYSTEM_PROMPT const systemPrompt = customPrompt.trim() || DEFAULT_SYSTEM_PROMPT
// 可变的上下文统计信息放在 user message 里,保持 system prompt 稳定不变
// 这样 provider 端Anthropic/OpenAI能最大化命中 prompt cache降低费用
const triggerDesc =
triggerReason === 'silence'
? `你已经 ${silentDays} 天没有和「${resolvedDisplayName}」聊天了。`
: `你最近和「${resolvedDisplayName}」有新的聊天动态。`
const todayStatsDesc =
sessionTriggerTimes.length > 1
? `今天你已经针对「${resolvedDisplayName}」收到过 ${sessionTriggerTimes.length - 1} 条见解(时间:${sessionTriggerTimes.slice(0, -1).join('、')}),请适当克制。`
: `今天你还没有针对「${resolvedDisplayName}」发出过见解。`
const globalStatsDesc = `今天全部联系人合计已触发 ${totalTodayTriggers} 条见解。`
const userPromptBase = [ const userPromptBase = [
`触发原因:${triggerDesc}`, triggerReason === 'silence' && silentDays
`时间统计:${todayStatsDesc}`, ? `${silentDays} 天未联系「${resolvedDisplayName}」。`
`全局统计:${globalStatsDesc}`, : '',
contextSection, contextSection,
momentsContextSection, momentsContextSection,
socialContextSection, socialContextSection,
@@ -1250,7 +1215,7 @@ ${topMentionText}
`接口地址:${endpoint}`, `接口地址:${endpoint}`,
`模型:${model}`, `模型:${model}`,
`Max Tokens${maxTokens}`, `Max Tokens${maxTokens}`,
`触发原因${triggerReason}`, `触发类型${triggerReason}`,
`上下文开关:${allowContext ? '开启' : '关闭'}`, `上下文开关:${allowContext ? '开启' : '关闭'}`,
`上下文条数:${contextCount}`, `上下文条数:${contextCount}`,
'', '',
@@ -1314,6 +1279,7 @@ ${topMentionText}
} }
insightLog('INFO', `已为 ${resolvedDisplayName} 推送见解`) insightLog('INFO', `已为 ${resolvedDisplayName} 推送见解`)
this.recordTrigger(sessionId)
} catch (e) { } catch (e) {
insightDebugSection( insightDebugSection(
'ERROR', 'ERROR',

View File

@@ -916,16 +916,18 @@
} }
.insight-collapsible-setting { .insight-collapsible-setting {
display: grid; max-height: 0;
grid-template-rows: 0fr;
opacity: 0; opacity: 0;
transform: translateY(-4px); overflow: hidden;
transition: grid-template-rows 0.22s ease, opacity 0.18s ease, transform 0.2s ease; transform: translate3d(0, -4px, 0);
contain: layout paint;
will-change: max-height, opacity, transform;
transition: max-height 0.2s ease, opacity 0.18s ease, transform 0.2s ease;
&.expanded { &.expanded {
grid-template-rows: 1fr; max-height: 128px;
opacity: 1; opacity: 1;
transform: translateY(0); transform: translate3d(0, 0, 0);
} }
&.collapsed { &.collapsed {
@@ -934,8 +936,8 @@
} }
.insight-collapsible-setting-inner { .insight-collapsible-setting-inner {
min-height: 0; padding-top: 2px;
overflow: hidden; backface-visibility: hidden;
} }
/* Premium Switch Style */ /* Premium Switch Style */

View File

@@ -3301,7 +3301,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<span className="form-hint"> <span className="form-hint">
N AI N AI
<br /> <br />
<strong></strong>AI <strong></strong>
<br /> <br />
<strong></strong> API <strong></strong> API
</span> </span>
@@ -3352,8 +3352,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="form-group"> <div className="form-group">
<label></label> <label></label>
<span className="form-hint"> <span className="form-hint">
</span> </span>
<div className="log-toggle-line"> <div className="log-toggle-line">
<span className="log-status">{aiInsightAllowMomentsContext ? '已开启' : '已关闭'}</span> <span className="log-status">{aiInsightAllowMomentsContext ? '已开启' : '已关闭'}</span>
@@ -3377,7 +3376,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="form-group"> <div className="form-group">
<label></label> <label></label>
<span className="form-hint"> <span className="form-hint">
XML AI token
</span> </span>
<input <input
type="number" type="number"
@@ -3873,9 +3872,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="api-docs"> <div className="api-docs">
<div className="api-item"> <div className="api-item">
<p className="api-desc" style={{ lineHeight: 1.7 }}> <p className="api-desc" style={{ lineHeight: 1.7 }}>
<strong></strong> 500ms <br /> <strong></strong> 2 <br />
<strong></strong> 4 <br /> <strong></strong> 4 <br />
<strong></strong> AI AI <br /> <strong></strong> <br />
<strong></strong> API WeFlow <strong></strong> API WeFlow
</p> </p>
</div> </div>