mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-28 15:09:19 +00:00
feat(insight): add moments context gating and prompt integration
This commit is contained in:
@@ -85,7 +85,13 @@ interface ConfigSchema {
|
||||
aiInsightApiModel: string
|
||||
aiInsightSilenceDays: number
|
||||
aiInsightAllowContext: boolean
|
||||
aiInsightAllowMomentsContext: boolean
|
||||
aiInsightMomentsContextCount: number
|
||||
aiInsightMomentsBindings: Record<string, { enabled: boolean; updatedAt: number }>
|
||||
aiInsightAllowSocialContext: boolean
|
||||
aiInsightSocialContextCount: number
|
||||
aiInsightWeiboCookie: string
|
||||
aiInsightWeiboBindings: Record<string, { uid: string; screenName?: string; updatedAt: number }>
|
||||
aiInsightFilterMode: 'whitelist' | 'blacklist'
|
||||
aiInsightFilterList: string[]
|
||||
aiInsightWhitelistEnabled: boolean
|
||||
@@ -205,6 +211,9 @@ export class ConfigService {
|
||||
aiInsightApiModel: 'gpt-4o-mini',
|
||||
aiInsightSilenceDays: 3,
|
||||
aiInsightAllowContext: false,
|
||||
aiInsightAllowMomentsContext: false,
|
||||
aiInsightMomentsContextCount: 5,
|
||||
aiInsightMomentsBindings: {},
|
||||
aiInsightAllowSocialContext: false,
|
||||
aiInsightFilterMode: 'whitelist',
|
||||
aiInsightFilterList: [],
|
||||
|
||||
@@ -21,6 +21,7 @@ import { URL } from 'url'
|
||||
import { app, Notification } from 'electron'
|
||||
import { ConfigService } from './config'
|
||||
import { chatService, ChatSession, Message } from './chatService'
|
||||
import { snsService } from './snsService'
|
||||
import { weiboService } from './social/weiboService'
|
||||
|
||||
// ─── 常量 ────────────────────────────────────────────────────────────────────
|
||||
@@ -52,6 +53,9 @@ const INSIGHT_CONFIG_KEYS = new Set([
|
||||
'aiModelApiMaxTokens',
|
||||
'aiInsightFilterMode',
|
||||
'aiInsightFilterList',
|
||||
'aiInsightAllowMomentsContext',
|
||||
'aiInsightMomentsContextCount',
|
||||
'aiInsightMomentsBindings',
|
||||
'aiInsightAllowSocialContext',
|
||||
'aiInsightSocialContextCount',
|
||||
'aiInsightWeiboCookie',
|
||||
@@ -853,6 +857,61 @@ ${topMentionText}
|
||||
return new Date(parsed).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
private formatMomentsTimestamp(raw: unknown): string {
|
||||
const numeric = Number(raw)
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||
return ''
|
||||
}
|
||||
const ms = numeric > 1_000_000_000_000 ? numeric : numeric * 1000
|
||||
return new Date(ms).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
private extractMomentReadableText(post: { contentDesc?: unknown; linkTitle?: unknown }): string {
|
||||
const contentDesc = this.normalizeInsightText(String(post.contentDesc || '')).replace(/\s+/g, ' ').trim()
|
||||
if (contentDesc) return contentDesc
|
||||
|
||||
const linkTitle = this.normalizeInsightText(String(post.linkTitle || '')).replace(/\s+/g, ' ').trim()
|
||||
if (linkTitle) return `[链接] ${linkTitle}`
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
private async getMomentsContextSection(sessionId: string): Promise<string> {
|
||||
const allowMomentsContext = this.config.get('aiInsightAllowMomentsContext') === true
|
||||
if (!allowMomentsContext) return ''
|
||||
|
||||
const bindings =
|
||||
(this.config.get('aiInsightMomentsBindings') as Record<string, { enabled?: boolean }> | undefined) || {}
|
||||
const isEnabledForSession = bindings[sessionId]?.enabled === true
|
||||
if (!isEnabledForSession) return ''
|
||||
|
||||
const countRaw = Number(this.config.get('aiInsightMomentsContextCount') || 5)
|
||||
const momentsCount = Math.max(1, Math.min(20, Math.floor(countRaw) || 5))
|
||||
|
||||
try {
|
||||
const result = await snsService.getTimeline(momentsCount, 0, [sessionId])
|
||||
const posts = result.success && Array.isArray(result.timeline) ? result.timeline : []
|
||||
if (posts.length === 0) return ''
|
||||
|
||||
const lines = posts
|
||||
.map((post) => {
|
||||
const text = this.extractMomentReadableText(post as { contentDesc?: unknown; linkTitle?: unknown })
|
||||
if (!text) return ''
|
||||
const shortText = text.length > 180 ? `${text.slice(0, 180)}...` : text
|
||||
const time = this.formatMomentsTimestamp((post as { createTime?: unknown }).createTime)
|
||||
return time ? `[朋友圈 ${time}] ${shortText}` : `[朋友圈] ${shortText}`
|
||||
})
|
||||
.filter(Boolean) as string[]
|
||||
|
||||
if (lines.length === 0) return ''
|
||||
insightLog('INFO', `已加载 ${lines.length} 条朋友圈内容 (sessionId=${sessionId})`)
|
||||
return `以下是该联系人的朋友圈内容(仅人类可读原文,最近 ${lines.length} 条):\n${lines.join('\n')}`
|
||||
} catch (error) {
|
||||
insightLog('WARN', `拉取朋友圈内容失败 (sessionId=${sessionId}): ${(error as Error).message}`)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
private async getSocialContextSection(sessionId: string): Promise<string> {
|
||||
const allowSocialContext = this.config.get('aiInsightAllowSocialContext') === true
|
||||
if (!allowSocialContext) return ''
|
||||
@@ -1136,6 +1195,7 @@ ${topMentionText}
|
||||
}
|
||||
}
|
||||
|
||||
const momentsContextSection = await this.getMomentsContextSection(sessionId)
|
||||
const socialContextSection = await this.getSocialContextSection(sessionId)
|
||||
|
||||
// ── 默认 system prompt(稳定内容,有利于 provider 端 prompt cache 命中)────
|
||||
@@ -1170,6 +1230,7 @@ ${topMentionText}
|
||||
`时间统计:${todayStatsDesc}`,
|
||||
`全局统计:${globalStatsDesc}`,
|
||||
contextSection,
|
||||
momentsContextSection,
|
||||
socialContextSection,
|
||||
'请给出你的见解(≤80字):'
|
||||
].filter(Boolean).join('\n\n')
|
||||
|
||||
@@ -3617,7 +3617,12 @@
|
||||
|
||||
&.insight-social-tab {
|
||||
.anti-revoke-list-header {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(300px, 420px) auto;
|
||||
grid-template-columns: minmax(0, 1fr) 86px minmax(240px, 340px) auto;
|
||||
|
||||
.insight-moments-column-title {
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.insight-social-column-title {
|
||||
color: var(--text-tertiary);
|
||||
@@ -3626,7 +3631,7 @@
|
||||
|
||||
.anti-revoke-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(300px, 420px) auto;
|
||||
grid-template-columns: minmax(0, 1fr) 86px minmax(240px, 340px) auto;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
@@ -3635,6 +3640,67 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.insight-moments-cell {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.insight-moments-toggle {
|
||||
position: relative;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
input[type='checkbox'] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.check-indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 78%, var(--primary) 22%);
|
||||
background: color-mix(in srgb, var(--bg-primary) 86%, var(--bg-secondary) 14%);
|
||||
color: var(--on-primary, #fff);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.16s ease;
|
||||
|
||||
svg {
|
||||
opacity: 0;
|
||||
transform: scale(0.75);
|
||||
transition: opacity 0.16s ease, transform 0.16s ease;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked + .check-indicator {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
|
||||
|
||||
svg {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox']:focus-visible + .check-indicator {
|
||||
outline: 2px solid color-mix(in srgb, var(--primary) 42%, transparent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.insight-social-binding-cell {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
@@ -3653,7 +3719,7 @@
|
||||
.binding-platform-chip {
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
padding: 2px 7px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 84%, transparent);
|
||||
@@ -3663,7 +3729,7 @@
|
||||
.insight-social-binding-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 30px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: color-mix(in srgb, var(--bg-primary) 92%, var(--bg-secondary) 8%);
|
||||
@@ -3752,6 +3818,7 @@
|
||||
.anti-revoke-list-header {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
|
||||
.insight-moments-column-title,
|
||||
.insight-social-column-title {
|
||||
display: none;
|
||||
}
|
||||
@@ -3763,11 +3830,16 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.insight-moments-cell,
|
||||
.insight-social-binding-cell,
|
||||
.anti-revoke-row-status {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.insight-moments-cell {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.insight-social-binding-cell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -284,6 +284,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [aiModelApiMaxTokens, setAiModelApiMaxTokens] = useState(200)
|
||||
const [aiInsightSilenceDays, setAiInsightSilenceDays] = useState(3)
|
||||
const [aiInsightAllowContext, setAiInsightAllowContext] = useState(false)
|
||||
const [aiInsightAllowMomentsContext, setAiInsightAllowMomentsContext] = useState(false)
|
||||
const [aiInsightMomentsContextCount, setAiInsightMomentsContextCount] = useState(5)
|
||||
const [aiInsightMomentsBindings, setAiInsightMomentsBindings] = useState<Record<string, configService.AiInsightMomentsBinding>>({})
|
||||
const [isTestingInsight, setIsTestingInsight] = useState(false)
|
||||
const [insightTestResult, setInsightTestResult] = useState<{ success: boolean; message: string } | null>(null)
|
||||
const [showInsightApiKey, setShowInsightApiKey] = useState(false)
|
||||
@@ -549,6 +552,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const savedAiModelApiMaxTokens = await configService.getAiModelApiMaxTokens()
|
||||
const savedAiInsightSilenceDays = await configService.getAiInsightSilenceDays()
|
||||
const savedAiInsightAllowContext = await configService.getAiInsightAllowContext()
|
||||
const savedAiInsightAllowMomentsContext = await configService.getAiInsightAllowMomentsContext()
|
||||
const savedAiInsightMomentsContextCount = await configService.getAiInsightMomentsContextCount()
|
||||
const savedAiInsightMomentsBindings = await configService.getAiInsightMomentsBindings()
|
||||
const savedAiInsightFilterMode = await configService.getAiInsightFilterMode()
|
||||
const savedAiInsightFilterList = await configService.getAiInsightFilterList()
|
||||
const savedAiInsightCooldownMinutes = await configService.getAiInsightCooldownMinutes()
|
||||
@@ -573,6 +579,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setAiModelApiMaxTokens(savedAiModelApiMaxTokens)
|
||||
setAiInsightSilenceDays(savedAiInsightSilenceDays)
|
||||
setAiInsightAllowContext(savedAiInsightAllowContext)
|
||||
setAiInsightAllowMomentsContext(savedAiInsightAllowMomentsContext)
|
||||
setAiInsightMomentsContextCount(savedAiInsightMomentsContextCount)
|
||||
setAiInsightMomentsBindings(savedAiInsightMomentsBindings)
|
||||
setAiInsightFilterMode(savedAiInsightFilterMode)
|
||||
setAiInsightFilterList(new Set(savedAiInsightFilterList))
|
||||
setAiInsightCooldownMinutes(savedAiInsightCooldownMinutes)
|
||||
@@ -3081,6 +3090,24 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
const isMomentsEnabledForSession = (sessionId: string): boolean => {
|
||||
return aiInsightMomentsBindings[sessionId]?.enabled === true
|
||||
}
|
||||
|
||||
const handleToggleMomentsBinding = async (sessionId: string, enabled: boolean) => {
|
||||
const nextBindings = { ...aiInsightMomentsBindings }
|
||||
if (enabled) {
|
||||
nextBindings[sessionId] = {
|
||||
enabled: true,
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
} else {
|
||||
delete nextBindings[sessionId]
|
||||
}
|
||||
setAiInsightMomentsBindings(nextBindings)
|
||||
await configService.setAiInsightMomentsBindings(nextBindings)
|
||||
}
|
||||
|
||||
const handleSaveWeiboBinding = async (sessionId: string, displayName: string) => {
|
||||
const draftUid = getWeiboBindingDraftValue(sessionId)
|
||||
setWeiboBindingLoadingSessionId(sessionId)
|
||||
@@ -3319,6 +3346,53 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="form-group">
|
||||
<label>允许发送近期朋友圈内容用于分析(实验性)</label>
|
||||
<span className="form-hint">
|
||||
仅对列表中勾选了「朋友圈」且会触发见解的私聊联系人生效。
|
||||
程序只会在触发见解时按需读取最近朋友圈内容,不会做后台持续扫描。
|
||||
</span>
|
||||
<div className="log-toggle-line">
|
||||
<span className="log-status">{aiInsightAllowMomentsContext ? '已开启' : '已关闭'}</span>
|
||||
<label className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={aiInsightAllowMomentsContext}
|
||||
onChange={async (e) => {
|
||||
const val = e.target.checked
|
||||
setAiInsightAllowMomentsContext(val)
|
||||
await configService.setAiInsightAllowMomentsContext(val)
|
||||
}}
|
||||
/>
|
||||
<span className="switch-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aiInsightAllowMomentsContext && (
|
||||
<div className="form-group">
|
||||
<label>发送近期朋友圈条数</label>
|
||||
<span className="form-hint">
|
||||
仅提取人类可读文本原文,不会拼接朋友圈原始 XML 字段。
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
className="field-input"
|
||||
value={aiInsightMomentsContextCount}
|
||||
min={1}
|
||||
max={20}
|
||||
onChange={(e) => {
|
||||
const val = Math.max(1, Math.min(20, parseInt(e.target.value, 10) || 5))
|
||||
setAiInsightMomentsContextCount(val)
|
||||
scheduleConfigSave('aiInsightMomentsContextCount', () => configService.setAiInsightMomentsContextCount(val))
|
||||
}}
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="form-group">
|
||||
<label>允许发送近期社交平台内容用于分析(实验性)</label>
|
||||
<span className="form-hint">
|
||||
@@ -3652,11 +3726,14 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<>
|
||||
<div className="anti-revoke-list-header">
|
||||
<span>对话({filteredSessions.length})</span>
|
||||
<span className="insight-moments-column-title">朋友圈</span>
|
||||
<span className="insight-social-column-title">社交平台(微博)</span>
|
||||
<span>状态</span>
|
||||
</div>
|
||||
{filteredSessions.map((session) => {
|
||||
const isSelected = aiInsightFilterList.has(session.username)
|
||||
const isPrivateSession = session.type === 'private'
|
||||
const isMomentsEnabled = isMomentsEnabledForSession(session.username)
|
||||
const weiboBinding = aiInsightWeiboBindings[session.username]
|
||||
const weiboDraftValue = getWeiboBindingDraftValue(session.username)
|
||||
const isBindingLoading = weiboBindingLoadingSessionId === session.username
|
||||
@@ -3695,8 +3772,24 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<span className="desc">{getSessionFilterTypeLabel(session.type)}</span>
|
||||
</div>
|
||||
</label>
|
||||
<div className="insight-moments-cell">
|
||||
{isPrivateSession ? (
|
||||
<label className="insight-moments-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isMomentsEnabled}
|
||||
onChange={(e) => { void handleToggleMomentsBinding(session.username, e.target.checked) }}
|
||||
/>
|
||||
<span className="check-indicator" aria-hidden="true">
|
||||
<Check size={12} />
|
||||
</span>
|
||||
</label>
|
||||
) : (
|
||||
<span className="binding-feedback muted">-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="insight-social-binding-cell">
|
||||
{session.type === 'private' ? (
|
||||
{isPrivateSession ? (
|
||||
<>
|
||||
<div className="insight-social-binding-input-wrap">
|
||||
<span className="binding-platform-chip">微博</span>
|
||||
|
||||
@@ -97,6 +97,9 @@ export const CONFIG_KEYS = {
|
||||
AI_INSIGHT_API_MODEL: 'aiInsightApiModel',
|
||||
AI_INSIGHT_SILENCE_DAYS: 'aiInsightSilenceDays',
|
||||
AI_INSIGHT_ALLOW_CONTEXT: 'aiInsightAllowContext',
|
||||
AI_INSIGHT_ALLOW_MOMENTS_CONTEXT: 'aiInsightAllowMomentsContext',
|
||||
AI_INSIGHT_MOMENTS_CONTEXT_COUNT: 'aiInsightMomentsContextCount',
|
||||
AI_INSIGHT_MOMENTS_BINDINGS: 'aiInsightMomentsBindings',
|
||||
AI_INSIGHT_ALLOW_SOCIAL_CONTEXT: 'aiInsightAllowSocialContext',
|
||||
AI_INSIGHT_FILTER_MODE: 'aiInsightFilterMode',
|
||||
AI_INSIGHT_FILTER_LIST: 'aiInsightFilterList',
|
||||
@@ -132,6 +135,11 @@ export interface AiInsightWeiboBinding {
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
export interface AiInsightMomentsBinding {
|
||||
enabled: boolean
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
export interface ExportDefaultMediaConfig {
|
||||
images: boolean
|
||||
videos: boolean
|
||||
@@ -1922,6 +1930,24 @@ export async function setAiInsightAllowContext(allow: boolean): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.AI_INSIGHT_ALLOW_CONTEXT, allow)
|
||||
}
|
||||
|
||||
export async function getAiInsightAllowMomentsContext(): Promise<boolean> {
|
||||
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_ALLOW_MOMENTS_CONTEXT)
|
||||
return value === true
|
||||
}
|
||||
|
||||
export async function setAiInsightAllowMomentsContext(allow: boolean): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.AI_INSIGHT_ALLOW_MOMENTS_CONTEXT, allow)
|
||||
}
|
||||
|
||||
export async function getAiInsightMomentsContextCount(): Promise<number> {
|
||||
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_MOMENTS_CONTEXT_COUNT)
|
||||
return typeof value === 'number' && value > 0 ? value : 5
|
||||
}
|
||||
|
||||
export async function setAiInsightMomentsContextCount(count: number): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.AI_INSIGHT_MOMENTS_CONTEXT_COUNT, count)
|
||||
}
|
||||
|
||||
export async function getAiInsightAllowSocialContext(): Promise<boolean> {
|
||||
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_ALLOW_SOCIAL_CONTEXT)
|
||||
return value === true
|
||||
@@ -2067,6 +2093,33 @@ export async function setAiInsightWeiboBindings(bindings: Record<string, AiInsig
|
||||
await config.set(CONFIG_KEYS.AI_INSIGHT_WEIBO_BINDINGS, bindings)
|
||||
}
|
||||
|
||||
const normalizeAiInsightMomentsBindings = (value: unknown): Record<string, AiInsightMomentsBinding> => {
|
||||
if (!value || typeof value !== 'object') return {}
|
||||
const result: Record<string, AiInsightMomentsBinding> = {}
|
||||
for (const [sessionIdRaw, bindingRaw] of Object.entries(value as Record<string, unknown>)) {
|
||||
const sessionId = String(sessionIdRaw || '').trim()
|
||||
if (!sessionId) continue
|
||||
if (!bindingRaw || typeof bindingRaw !== 'object') continue
|
||||
const bindingObj = bindingRaw as { enabled?: unknown; updatedAt?: unknown }
|
||||
if (bindingObj.enabled !== true) continue
|
||||
const updatedAtRaw = Number(bindingObj.updatedAt)
|
||||
result[sessionId] = {
|
||||
enabled: true,
|
||||
updatedAt: Number.isFinite(updatedAtRaw) && updatedAtRaw > 0 ? Math.floor(updatedAtRaw) : Date.now()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getAiInsightMomentsBindings(): Promise<Record<string, AiInsightMomentsBinding>> {
|
||||
const value = await config.get(CONFIG_KEYS.AI_INSIGHT_MOMENTS_BINDINGS)
|
||||
return normalizeAiInsightMomentsBindings(value)
|
||||
}
|
||||
|
||||
export async function setAiInsightMomentsBindings(bindings: Record<string, AiInsightMomentsBinding>): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.AI_INSIGHT_MOMENTS_BINDINGS, normalizeAiInsightMomentsBindings(bindings))
|
||||
}
|
||||
|
||||
export async function getAiFootprintEnabled(): Promise<boolean> {
|
||||
const value = await config.get(CONFIG_KEYS.AI_FOOTPRINT_ENABLED)
|
||||
return value === true
|
||||
|
||||
Reference in New Issue
Block a user