Merge pull request #10 from Jasonzhu1207/v0/jasonzhu081207-4751-c8eef8af

Enable AI insights and Telegram push notifications
This commit is contained in:
Jason
2026-04-06 18:59:09 +08:00
committed by GitHub
2 changed files with 28 additions and 17 deletions

View File

@@ -427,17 +427,22 @@ class InsightService {
// ── 沉默联系人扫描 ────────────────────────────────────────────────────────── // ── 沉默联系人扫描 ──────────────────────────────────────────────────────────
private scheduleSilenceScan(): void { private scheduleSilenceScan(): void {
this.silenceInitialDelayTimer = setTimeout(() => { // 等待扫描完成后再安排下一次,避免并发堆积
void this.runSilenceScan() const scheduleNext = () => {
// 每次扫描完毕后重新读取间隔配置,允许用户动态调整不需要重启 if (!this.started) return
const scheduleNext = () => { const intervalHours = (this.config.get('aiInsightScanIntervalHours') as number) || 4
const intervalHours = (this.config.get('aiInsightScanIntervalHours') as number) || 4 const intervalMs = Math.max(0.1, intervalHours) * 60 * 60 * 1000
const intervalMs = Math.max(0.1, intervalHours) * 60 * 60 * 1000 insightLog('INFO', `下次沉默扫描将在 ${intervalHours} 小时后执行`)
insightLog('INFO', `下次沉默扫描将在 ${intervalHours} 小时后执行`) this.silenceScanTimer = setTimeout(async () => {
this.silenceScanTimer = setTimeout(() => { this.silenceScanTimer = null
void this.runSilenceScan().then(scheduleNext) await this.runSilenceScan()
}, intervalMs) scheduleNext()
} }, intervalMs)
}
this.silenceInitialDelayTimer = setTimeout(async () => {
this.silenceInitialDelayTimer = null
await this.runSilenceScan()
scheduleNext() scheduleNext()
}, SILENCE_SCAN_INITIAL_DELAY_MS) }, SILENCE_SCAN_INITIAL_DELAY_MS)
} }
@@ -711,8 +716,9 @@ class InsightService {
const telegramChatIds = (this.config.get('aiInsightTelegramChatIds') as string) || '' const telegramChatIds = (this.config.get('aiInsightTelegramChatIds') as string) || ''
if (telegramToken && telegramChatIds) { if (telegramToken && telegramChatIds) {
const chatIds = telegramChatIds.split(',').map((s) => s.trim()).filter(Boolean) const chatIds = telegramChatIds.split(',').map((s) => s.trim()).filter(Boolean)
const telegramText = `【WeFlow】 ${notifTitle}\n\n${insight}`
for (const chatId of chatIds) { for (const chatId of chatIds) {
this.sendTelegram(telegramToken, chatId, `${notifTitle}\n\n${insight}`).catch((e) => { this.sendTelegram(telegramToken, chatId, telegramText).catch((e) => {
insightLog('WARN', `Telegram 推送失败 (chatId=${chatId}): ${(e as Error).message}`) insightLog('WARN', `Telegram 推送失败 (chatId=${chatId}): ${(e as Error).message}`)
}) })
} }

View File

@@ -1467,7 +1467,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</div> </div>
<div className="form-group quote-layout-group"> <div className="form-group quote-layout-group">
<label><EFBFBD><EFBFBD><EFBFBD></label> <label></label>
<span className="form-hint"></span> <span className="form-hint"></span>
<div className="quote-layout-picker" role="radiogroup" aria-label="引用样式选择"> <div className="quote-layout-picker" role="radiogroup" aria-label="引用样式选择">
{[ {[
@@ -2825,6 +2825,10 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
2. 控制在 80 字以内,直接、具体、一针见血。不要废话。 2. 控制在 80 字以内,直接、具体、一针见血。不要废话。
3. 输出纯文本,不使用 Markdown。 3. 输出纯文本,不使用 Markdown。
4. 只有在完全没有任何可说的内容时(比如对话只有一条"嗯"),才回复"SKIP"。绝大多数情况下你应该输出见解。` 4. 只有在完全没有任何可说的内容时(比如对话只有一条"嗯"),才回复"SKIP"。绝大多数情况下你应该输出见解。`
// 展示值:有自定义内容时显示自定义内容,否则显示默认值(可直接编辑)
const displayValue = aiInsightSystemPrompt || DEFAULT_SYSTEM_PROMPT
return ( return (
<div className="form-group"> <div className="form-group">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
@@ -2833,6 +2837,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
className="button-secondary" className="button-secondary"
style={{ fontSize: 12, padding: '3px 10px' }} style={{ fontSize: 12, padding: '3px 10px' }}
onClick={async () => { onClick={async () => {
// 恢复默认清空自定义值UI 回到显示默认内容的状态
setAiInsightSystemPrompt('') setAiInsightSystemPrompt('')
await configService.setAiInsightSystemPrompt('') await configService.setAiInsightSystemPrompt('')
}} }}
@@ -2841,16 +2846,16 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</button> </button>
</div> </div>
<span className="form-hint"> <span className="form-hint">
使
</span> </span>
<textarea <textarea
className="field-input" className="field-input"
rows={8} rows={8}
style={{ width: '100%', resize: 'vertical', fontFamily: 'monospace', fontSize: 12 }} style={{ width: '100%', resize: 'vertical', fontFamily: 'monospace', fontSize: 12 }}
placeholder={DEFAULT_SYSTEM_PROMPT} value={displayValue}
value={aiInsightSystemPrompt}
onChange={(e) => { onChange={(e) => {
const val = e.target.value const val = e.target.value
// 如果用户把内容改得和默认值一样,仍存自定义值(不影响功能)
setAiInsightSystemPrompt(val) setAiInsightSystemPrompt(val)
scheduleConfigSave('aiInsightSystemPrompt', () => configService.setAiInsightSystemPrompt(val)) scheduleConfigSave('aiInsightSystemPrompt', () => configService.setAiInsightSystemPrompt(val))
}} }}
@@ -3117,7 +3122,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> <EFBFBD><EFBFBD>AI AI <br /> <strong></strong> AI AI <br />
<strong></strong> API WeFlow <strong></strong> API WeFlow
</p> </p>
</div> </div>