From b9af7ffc8c543ff4d58b9d14feb325276736810f Mon Sep 17 00:00:00 2001 From: cc <98377878+hicccc77@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:52:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main.ts | 16 + electron/preload.ts | 15 +- electron/services/config.ts | 44 ++- electron/services/exportRecordService.ts | 3 +- electron/services/insightService.ts | 132 ++++++- src/pages/MyFootprintPage.scss | 36 ++ src/pages/MyFootprintPage.tsx | 54 ++- src/pages/SettingsPage.scss | 66 ++++ src/pages/SettingsPage.tsx | 437 +++++++++++++++-------- src/services/config.ts | 72 +++- src/types/electron.d.ts | 18 + 11 files changed, 721 insertions(+), 172 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index dca37dc..f6a873a 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1635,6 +1635,22 @@ function registerIpcHandlers() { return insightService.triggerTest() }) + ipcMain.handle('insight:generateFootprintInsight', async (_, payload: { + rangeLabel: string + summary: { + private_inbound_people?: number + private_replied_people?: number + private_outbound_people?: number + private_reply_rate?: number + mention_count?: number + mention_group_count?: number + } + privateSegments?: Array<{ displayName?: string; session_id?: string; incoming_count?: number; outgoing_count?: number; message_count?: number; replied?: boolean }> + mentionGroups?: Array<{ displayName?: string; session_id?: string; count?: number }> + }) => { + return insightService.generateFootprintInsight(payload) + }) + ipcMain.handle('config:clear', async () => { if (isLaunchAtStartupSupported() && getSystemLaunchAtStartup()) { const result = setSystemLaunchAtStartup(false) diff --git a/electron/preload.ts b/electron/preload.ts index 9e45516..838a305 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -526,6 +526,19 @@ contextBridge.exposeInMainWorld('electronAPI', { insight: { testConnection: () => ipcRenderer.invoke('insight:testConnection'), getTodayStats: () => ipcRenderer.invoke('insight:getTodayStats'), - triggerTest: () => ipcRenderer.invoke('insight:triggerTest') + triggerTest: () => ipcRenderer.invoke('insight:triggerTest'), + generateFootprintInsight: (payload: { + rangeLabel: string + summary: { + private_inbound_people?: number + private_replied_people?: number + private_outbound_people?: number + private_reply_rate?: number + mention_count?: number + mention_group_count?: number + } + privateSegments?: Array<{ displayName?: string; session_id?: string; incoming_count?: number; outgoing_count?: number; message_count?: number; replied?: boolean }> + mentionGroups?: Array<{ displayName?: string; session_id?: string; count?: number }> + }) => ipcRenderer.invoke('insight:generateFootprintInsight', payload) } }) diff --git a/electron/services/config.ts b/electron/services/config.ts index c096d06..fb05832 100644 --- a/electron/services/config.ts +++ b/electron/services/config.ts @@ -71,6 +71,9 @@ interface ConfigSchema { exportWriteLayout: 'A' | 'B' | 'C' // AI 见解 + aiModelApiBaseUrl: string + aiModelApiKey: string + aiModelApiModel: string aiInsightEnabled: boolean aiInsightApiBaseUrl: string aiInsightApiKey: string @@ -93,10 +96,21 @@ interface ConfigSchema { aiInsightTelegramToken: string /** Telegram 接收 Chat ID,逗号分隔,支持多个 */ aiInsightTelegramChatIds: string + + // AI 足迹 + aiFootprintEnabled: boolean + aiFootprintSystemPrompt: string } // 需要 safeStorage 加密的字段(普通模式) -const ENCRYPTED_STRING_KEYS: Set = new Set(['decryptKey', 'imageAesKey', 'authPassword', 'httpApiToken', 'aiInsightApiKey']) +const ENCRYPTED_STRING_KEYS: Set = new Set([ + 'decryptKey', + 'imageAesKey', + 'authPassword', + 'httpApiToken', + 'aiModelApiKey', + 'aiInsightApiKey' +]) const ENCRYPTED_BOOL_KEYS: Set = new Set(['authEnabled', 'authUseHello']) const ENCRYPTED_NUMBER_KEYS: Set = new Set(['imageXorKey']) @@ -167,6 +181,9 @@ export class ConfigService { quoteLayout: 'quote-top', wordCloudExcludeWords: [], exportWriteLayout: 'A', + aiModelApiBaseUrl: '', + aiModelApiKey: '', + aiModelApiModel: 'gpt-4o-mini', aiInsightEnabled: false, aiInsightApiBaseUrl: '', aiInsightApiKey: '', @@ -181,7 +198,9 @@ export class ConfigService { aiInsightSystemPrompt: '', aiInsightTelegramEnabled: false, aiInsightTelegramToken: '', - aiInsightTelegramChatIds: '' + aiInsightTelegramChatIds: '', + aiFootprintEnabled: false, + aiFootprintSystemPrompt: '' } const storeOptions: any = { @@ -213,6 +232,7 @@ export class ConfigService { } } this.migrateAuthFields() + this.migrateAiConfig() } // === 状态查询 === @@ -717,6 +737,26 @@ export class ConfigService { } } + private migrateAiConfig(): void { + const sharedBaseUrl = String(this.get('aiModelApiBaseUrl') || '').trim() + const sharedApiKey = String(this.get('aiModelApiKey') || '').trim() + const sharedModel = String(this.get('aiModelApiModel') || '').trim() + + const legacyBaseUrl = String(this.get('aiInsightApiBaseUrl') || '').trim() + const legacyApiKey = String(this.get('aiInsightApiKey') || '').trim() + const legacyModel = String(this.get('aiInsightApiModel') || '').trim() + + if (!sharedBaseUrl && legacyBaseUrl) { + this.set('aiModelApiBaseUrl', legacyBaseUrl) + } + if (!sharedApiKey && legacyApiKey) { + this.set('aiModelApiKey', legacyApiKey) + } + if (!sharedModel && legacyModel) { + this.set('aiModelApiModel', legacyModel) + } + } + // === 验证 === verifyAuthEnabled(): boolean { diff --git a/electron/services/exportRecordService.ts b/electron/services/exportRecordService.ts index 23c82a9..5ff1049 100644 --- a/electron/services/exportRecordService.ts +++ b/electron/services/exportRecordService.ts @@ -19,7 +19,8 @@ class ExportRecordService { private resolveFilePath(): string { if (this.filePath) return this.filePath - const userDataPath = app.getPath('userData') + const workerUserDataPath = String(process.env.WEFLOW_USER_DATA_PATH || process.env.WEFLOW_CONFIG_CWD || '').trim() + const userDataPath = workerUserDataPath || app?.getPath?.('userData') || process.cwd() fs.mkdirSync(userDataPath, { recursive: true }) this.filePath = path.join(userDataPath, 'weflow-export-records.json') return this.filePath diff --git a/electron/services/insightService.ts b/electron/services/insightService.ts index 47295ad..6890f7a 100644 --- a/electron/services/insightService.ts +++ b/electron/services/insightService.ts @@ -39,6 +39,9 @@ const DEFAULT_SILENCE_DAYS = 3 const INSIGHT_CONFIG_KEYS = new Set([ 'aiInsightEnabled', 'aiInsightScanIntervalHours', + 'aiModelApiBaseUrl', + 'aiModelApiKey', + 'aiModelApiModel', 'dbPath', 'decryptKey', 'myWxid' @@ -51,6 +54,12 @@ interface TodayTriggerRecord { timestamps: number[] } +interface SharedAiModelConfig { + apiBaseUrl: string + apiKey: string + model: string +} + // ─── 日志 ───────────────────────────────────────────────────────────────────── /** @@ -320,9 +329,7 @@ class InsightService { * 供设置页"测试连接"按钮调用。 */ async testConnection(): Promise<{ success: boolean; message: string }> { - const apiBaseUrl = this.config.get('aiInsightApiBaseUrl') as string - const apiKey = this.config.get('aiInsightApiKey') as string - const model = (this.config.get('aiInsightApiModel') as string) || 'gpt-4o-mini' + const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig() if (!apiBaseUrl || !apiKey) { return { success: false, message: '请先填写 API 地址和 API Key' } @@ -348,8 +355,7 @@ class InsightService { */ async triggerTest(): Promise<{ success: boolean; message: string }> { insightLog('INFO', '手动触发测试见解...') - const apiBaseUrl = this.config.get('aiInsightApiBaseUrl') as string - const apiKey = this.config.get('aiInsightApiKey') as string + const { apiBaseUrl, apiKey } = this.getSharedAiModelConfig() if (!apiBaseUrl || !apiKey) { return { success: false, message: '请先填写 API 地址和 Key' } } @@ -398,12 +404,124 @@ class InsightService { return result } + async generateFootprintInsight(params: { + rangeLabel: string + summary: { + private_inbound_people?: number + private_replied_people?: number + private_outbound_people?: number + private_reply_rate?: number + mention_count?: number + mention_group_count?: number + } + privateSegments?: Array<{ displayName?: string; session_id?: string; incoming_count?: number; outgoing_count?: number; message_count?: number; replied?: boolean }> + mentionGroups?: Array<{ displayName?: string; session_id?: string; count?: number }> + }): Promise<{ success: boolean; message: string; insight?: string }> { + const enabled = this.config.get('aiFootprintEnabled') === true + if (!enabled) { + return { success: false, message: '请先在设置中开启「AI 足迹总结」' } + } + + const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig() + if (!apiBaseUrl || !apiKey) { + return { success: false, message: '请先填写通用 AI 模型配置(API 地址和 Key)' } + } + + const summary = params?.summary || {} + const rangeLabel = String(params?.rangeLabel || '').trim() || '当前范围' + const privateSegments = Array.isArray(params?.privateSegments) ? params.privateSegments.slice(0, 6) : [] + const mentionGroups = Array.isArray(params?.mentionGroups) ? params.mentionGroups.slice(0, 6) : [] + + const topPrivateText = privateSegments.length > 0 + ? privateSegments + .map((item, idx) => { + const name = String(item.displayName || item.session_id || `联系人${idx + 1}`).trim() + const inbound = Number(item.incoming_count) || 0 + const outbound = Number(item.outgoing_count) || 0 + const total = Math.max(Number(item.message_count) || 0, inbound + outbound) + return `${idx + 1}. ${name}(收${inbound}/发${outbound}/总${total}${item.replied ? '/已回复' : ''})` + }) + .join('\n') + : '无' + + const topMentionText = mentionGroups.length > 0 + ? mentionGroups + .map((item, idx) => { + const name = String(item.displayName || item.session_id || `群聊${idx + 1}`).trim() + const count = Number(item.count) || 0 + return `${idx + 1}. ${name}(@我 ${count} 次)` + }) + .join('\n') + : '无' + + const defaultSystemPrompt = `你是用户的聊天足迹教练,负责基于统计数据给出一段简明复盘。 +要求: +1. 输出 2-3 句,总长度不超过 180 字。 +2. 必须包含:总体观察 + 一个可执行建议。 +3. 语气务实,不夸张,不使用 Markdown。` + const customPrompt = String(this.config.get('aiFootprintSystemPrompt') || '').trim() + const systemPrompt = customPrompt || defaultSystemPrompt + + const userPrompt = `统计范围:${rangeLabel} +有聊天的人数:${Number(summary.private_inbound_people) || 0} +我有回复的人数:${Number(summary.private_outbound_people) || 0} +回复率:${(((Number(summary.private_reply_rate) || 0) * 100)).toFixed(1)}% +@我次数:${Number(summary.mention_count) || 0} +涉及群聊:${Number(summary.mention_group_count) || 0} + +私聊重点: +${topPrivateText} + +群聊@我重点: +${topMentionText} + +请给出足迹复盘(2-3句,含建议):` + + try { + const result = await callApi( + apiBaseUrl, + apiKey, + model, + [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt } + ], + 25_000 + ) + const insight = result.trim().slice(0, 400) + if (!insight) return { success: false, message: '模型返回为空' } + return { success: true, message: '生成成功', insight } + } catch (error) { + return { success: false, message: `生成失败:${(error as Error).message}` } + } + } + // ── 私有方法 ──────────────────────────────────────────────────────────────── private isEnabled(): boolean { return this.config.get('aiInsightEnabled') === true } + private getSharedAiModelConfig(): SharedAiModelConfig { + const apiBaseUrl = String( + this.config.get('aiModelApiBaseUrl') + || this.config.get('aiInsightApiBaseUrl') + || '' + ).trim() + const apiKey = String( + this.config.get('aiModelApiKey') + || this.config.get('aiInsightApiKey') + || '' + ).trim() + const model = String( + this.config.get('aiModelApiModel') + || this.config.get('aiInsightApiModel') + || 'gpt-4o-mini' + ).trim() || 'gpt-4o-mini' + + return { apiBaseUrl, apiKey, model } + } + /** * 判断某个会话是否允许触发见解。 * 若白名单未启用,则所有私聊会话均允许; @@ -696,9 +814,7 @@ class InsightService { if (!sessionId) return if (!this.isEnabled()) return - const apiBaseUrl = this.config.get('aiInsightApiBaseUrl') as string - const apiKey = this.config.get('aiInsightApiKey') as string - const model = (this.config.get('aiInsightApiModel') as string) || 'gpt-4o-mini' + const { apiBaseUrl, apiKey, model } = this.getSharedAiModelConfig() const allowContext = this.config.get('aiInsightAllowContext') as boolean const contextCount = (this.config.get('aiInsightContextCount') as number) || 40 diff --git a/src/pages/MyFootprintPage.scss b/src/pages/MyFootprintPage.scss index 273d800..af1dd69 100644 --- a/src/pages/MyFootprintPage.scss +++ b/src/pages/MyFootprintPage.scss @@ -258,6 +258,42 @@ display: none; /* Minimalistic, hide icon in KPI */ } +.footprint-ai-result { + border-radius: 10px; + padding: 14px 16px; + background: color-mix(in srgb, var(--text-tertiary) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--border-color) 40%, transparent); + + .footprint-ai-head { + display: flex; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; + + strong { + font-size: 14px; + color: var(--text-primary); + } + + span { + font-size: 12px; + color: var(--text-tertiary); + } + } + + p { + margin: 0; + white-space: pre-wrap; + line-height: 1.6; + color: var(--text-secondary); + font-size: 13px; + } + + &.footprint-ai-result-error { + border-color: color-mix(in srgb, #ef4444 50%, transparent); + } +} + .kpi-label { font-size: 13px; font-weight: 500; diff --git a/src/pages/MyFootprintPage.tsx b/src/pages/MyFootprintPage.tsx index fd2967d..ff7918d 100644 --- a/src/pages/MyFootprintPage.tsx +++ b/src/pages/MyFootprintPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react' import { useNavigate } from 'react-router-dom' -import { AlertCircle, AtSign, CheckCircle2, Download, MessageCircle, RefreshCw, Search, Users } from 'lucide-react' +import { AlertCircle, AtSign, CheckCircle2, Download, Loader2, MessageCircle, RefreshCw, Search, Sparkles, Users } from 'lucide-react' import DateRangePicker from '../components/DateRangePicker' import './MyFootprintPage.scss' @@ -9,6 +9,7 @@ type TimelineMode = 'all' | 'mention' | 'private' type TimelineTimeMode = 'clock' | 'month_day_clock' | 'full_date_clock' type PrivateDotVariant = 'both' | 'inbound_only' | 'outbound_only' type ExportModalStatus = 'idle' | 'progress' | 'success' | 'error' +type FootprintAiStatus = 'idle' | 'loading' | 'success' | 'error' interface MyFootprintSummary { private_inbound_people: number @@ -336,6 +337,8 @@ function MyFootprintPage() { const [exportModalDescription, setExportModalDescription] = useState('') const [exportModalPath, setExportModalPath] = useState('') const [error, setError] = useState(null) + const [footprintAiStatus, setFootprintAiStatus] = useState('idle') + const [footprintAiText, setFootprintAiText] = useState('') const inflightRangeKeyRef = useRef(null) const currentRange = useMemo(() => buildRange(preset, customStartDate, customEndDate), [preset, customStartDate, customEndDate]) @@ -638,6 +641,41 @@ function MyFootprintPage() { } }, [currentRange.begin, currentRange.end, currentRange.label]) + const handleGenerateAiSummary = useCallback(async () => { + setFootprintAiStatus('loading') + setFootprintAiText('') + try { + const privateSegments = (data.private_segments.length > 0 ? data.private_segments : data.private_sessions).slice(0, 12) + const result = await window.electronAPI.insight.generateFootprintInsight({ + rangeLabel: currentRange.label, + summary: data.summary, + privateSegments: privateSegments.map((item: MyFootprintPrivateSegment | MyFootprintPrivateSession) => ({ + session_id: item.session_id, + displayName: item.displayName, + incoming_count: item.incoming_count, + outgoing_count: item.outgoing_count, + message_count: 'message_count' in item ? item.message_count : item.incoming_count + item.outgoing_count, + replied: item.replied + })), + mentionGroups: data.mention_groups.slice(0, 12).map((item) => ({ + session_id: item.session_id, + displayName: item.displayName, + count: item.count + })) + }) + if (!result.success || !result.insight) { + setFootprintAiStatus('error') + setFootprintAiText(result.message || '生成失败') + return + } + setFootprintAiStatus('success') + setFootprintAiText(result.insight) + } catch (generateError) { + setFootprintAiStatus('error') + setFootprintAiText(String(generateError)) + } + }, [currentRange.label, data]) + return (
@@ -690,6 +728,10 @@ function MyFootprintPage() { 刷新 +
+ {footprintAiStatus !== 'idle' && ( +
+
+ AI 足迹总结 + {currentRange.label} +
+

{footprintAiText}

+
+ )} +
; label: string; icon: React.ElementType }[] = [ { id: 'appearance', label: '外观', icon: Palette }, { id: 'notification', label: '通知', icon: Bell }, { id: 'antiRevoke', label: '防撤回', icon: RotateCcw }, @@ -27,12 +41,17 @@ const tabs: { id: SettingsTab; label: string; icon: React.ElementType }[] = [ { id: 'cache', label: '缓存', icon: HardDrive }, { id: 'api', label: 'API 服务', icon: Globe }, { id: 'analytics', label: '分析', icon: BarChart2 }, - { id: 'insight', label: 'AI 见解', icon: Sparkles }, { id: 'security', label: '安全', icon: ShieldCheck }, { id: 'updates', label: '版本更新', icon: RefreshCw }, { id: 'about', label: '关于', icon: Info } ] +const aiTabs: Array<{ id: Extract; label: string }> = [ + { id: 'aiCommon', label: 'AI 通用' }, + { id: 'insight', label: 'AI 见解' }, + { id: 'aiFootprint', label: 'AI 足迹' } +] + const isMac = navigator.userAgent.toLowerCase().includes('mac') const isLinux = navigator.userAgent.toLowerCase().includes('linux') const isWindows = !isMac && !isLinux @@ -88,6 +107,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const clearAnalyticsStoreCache = useAnalyticsStore((state) => state.clearCache) const [activeTab, setActiveTab] = useState('appearance') + const [aiGroupExpanded, setAiGroupExpanded] = useState(false) const [decryptKey, setDecryptKey] = useState('') const [imageXorKey, setImageXorKey] = useState('') const [imageAesKey, setImageAesKey] = useState('') @@ -217,9 +237,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { // AI 见解 state const [aiInsightEnabled, setAiInsightEnabled] = useState(false) - const [aiInsightApiBaseUrl, setAiInsightApiBaseUrl] = useState('') - const [aiInsightApiKey, setAiInsightApiKey] = useState('') - const [aiInsightApiModel, setAiInsightApiModel] = useState('gpt-4o-mini') + const [aiModelApiBaseUrl, setAiModelApiBaseUrl] = useState('') + const [aiModelApiKey, setAiModelApiKey] = useState('') + const [aiModelApiModel, setAiModelApiModel] = useState('gpt-4o-mini') const [aiInsightSilenceDays, setAiInsightSilenceDays] = useState(3) const [aiInsightAllowContext, setAiInsightAllowContext] = useState(false) const [isTestingInsight, setIsTestingInsight] = useState(false) @@ -237,6 +257,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const [aiInsightTelegramEnabled, setAiInsightTelegramEnabled] = useState(false) const [aiInsightTelegramToken, setAiInsightTelegramToken] = useState('') const [aiInsightTelegramChatIds, setAiInsightTelegramChatIds] = useState('') + const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false) + const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('') // 检查 Hello 可用性 useEffect(() => { @@ -276,6 +298,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { setActiveTab(initialTab) }, [location.state]) + useEffect(() => { + if (activeTab === 'aiCommon' || activeTab === 'insight' || activeTab === 'aiFootprint') { + setAiGroupExpanded(true) + } + }, [activeTab]) + useEffect(() => { if (!onClose) return const handleKeyDown = (event: KeyboardEvent) => { @@ -448,9 +476,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { // 加载 AI 见解配置 const savedAiInsightEnabled = await configService.getAiInsightEnabled() - const savedAiInsightApiBaseUrl = await configService.getAiInsightApiBaseUrl() - const savedAiInsightApiKey = await configService.getAiInsightApiKey() - const savedAiInsightApiModel = await configService.getAiInsightApiModel() + const savedAiModelApiBaseUrl = await configService.getAiModelApiBaseUrl() + const savedAiModelApiKey = await configService.getAiModelApiKey() + const savedAiModelApiModel = await configService.getAiModelApiModel() const savedAiInsightSilenceDays = await configService.getAiInsightSilenceDays() const savedAiInsightAllowContext = await configService.getAiInsightAllowContext() const savedAiInsightWhitelistEnabled = await configService.getAiInsightWhitelistEnabled() @@ -462,10 +490,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const savedAiInsightTelegramEnabled = await configService.getAiInsightTelegramEnabled() const savedAiInsightTelegramToken = await configService.getAiInsightTelegramToken() const savedAiInsightTelegramChatIds = await configService.getAiInsightTelegramChatIds() + const savedAiFootprintEnabled = await configService.getAiFootprintEnabled() + const savedAiFootprintSystemPrompt = await configService.getAiFootprintSystemPrompt() setAiInsightEnabled(savedAiInsightEnabled) - setAiInsightApiBaseUrl(savedAiInsightApiBaseUrl) - setAiInsightApiKey(savedAiInsightApiKey) - setAiInsightApiModel(savedAiInsightApiModel) + setAiModelApiBaseUrl(savedAiModelApiBaseUrl) + setAiModelApiKey(savedAiModelApiKey) + setAiModelApiModel(savedAiModelApiModel) setAiInsightSilenceDays(savedAiInsightSilenceDays) setAiInsightAllowContext(savedAiInsightAllowContext) setAiInsightWhitelistEnabled(savedAiInsightWhitelistEnabled) @@ -477,6 +507,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { setAiInsightTelegramEnabled(savedAiInsightTelegramEnabled) setAiInsightTelegramToken(savedAiInsightTelegramToken) setAiInsightTelegramChatIds(savedAiInsightTelegramChatIds) + setAiFootprintEnabled(savedAiFootprintEnabled) + setAiFootprintSystemPrompt(savedAiFootprintSystemPrompt) } catch (e: any) { console.error('加载配置失败:', e) @@ -2498,6 +2530,118 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { } } + const renderAiCommonTab = () => ( +
+
+ + + 这是「AI 见解」与「AI 足迹总结」共享的模型接入配置。填写 OpenAI 兼容接口的 Base URL,末尾不要加斜杠。 + 程序会自动拼接 /chat/completions。 +
+ 示例:https://api.ohmygpt.com/v1https://api.openai.com/v1 +
+ { + const val = e.target.value + setAiModelApiBaseUrl(val) + scheduleConfigSave('aiModelApiBaseUrl', () => configService.setAiModelApiBaseUrl(val)) + }} + /> +
+ +
+ + + 你的 API Key,保存后经过系统加密存储,不会明文写入磁盘。 + +
+ { + const val = e.target.value + setAiModelApiKey(val) + scheduleConfigSave('aiModelApiKey', () => configService.setAiModelApiKey(val)) + }} + style={{ flex: 1 }} + /> + + {aiModelApiKey && ( + + )} +
+
+ +
+ + + 填写你的 API 提供商支持的模型名,将同时用于见解和足迹模块。 +
+ 常用示例:gpt-4o-minigpt-4odeepseek-chatclaude-3-5-haiku-20241022 +
+ { + const val = e.target.value.trim() || 'gpt-4o-mini' + setAiModelApiModel(val) + scheduleConfigSave('aiModelApiModel', () => configService.setAiModelApiModel(val)) + }} + style={{ width: 260 }} + /> +
+ +
+ + + 测试通用模型连接,见解与足迹都会使用这套配置。 + +
+ + {insightTestResult && ( + + {insightTestResult.success ? : } + {insightTestResult.message} + + )} +
+
+
+ ) + const renderInsightTab = () => (
{/* 总开关 */} @@ -2526,149 +2670,41 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
- {/* API 配置 */} -
- - - 填写 OpenAI 兼容接口的 Base URL,末尾不要加斜杠。 - 程序会自动拼接 /chat/completions。 -
- 示例:https://api.ohmygpt.com/v1https://api.openai.com/v1 -
- { - const val = e.target.value - setAiInsightApiBaseUrl(val) - scheduleConfigSave('aiInsightApiBaseUrl', () => configService.setAiInsightApiBaseUrl(val)) - }} - style={{ fontFamily: 'monospace' }} - /> -
- -
- - - 你的 API Key,保存后经过系统加密存储,不会明文写入磁盘。 - -
- { - const val = e.target.value - setAiInsightApiKey(val) - scheduleConfigSave('aiInsightApiKey', () => configService.setAiInsightApiKey(val)) - }} - style={{ flex: 1, fontFamily: 'monospace' }} - /> - - {aiInsightApiKey && ( - - )} -
-
- -
- - - 填写你的 API 提供商支持的模型名,建议使用综合能力较强的模型以获得有洞察力的见解。 -
- 常用示例:gpt-4o-minigpt-4odeepseek-chatclaude-3-5-haiku-20241022 -
- { - const val = e.target.value.trim() || 'gpt-4o-mini' - setAiInsightApiModel(val) - scheduleConfigSave('aiInsightApiModel', () => configService.setAiInsightApiModel(val)) - }} - style={{ width: 260, fontFamily: 'monospace' }} - /> -
- - {/* 测试连接 + 触发测试 */}
- 先用"测试 API 连接"确认 Key 和 URL 填写正确,再用"立即触发测试见解"验证完整链路(数据库→API→弹窗)。触发后请留意右下角通知弹窗。 + 该功能依赖「AI 通用」里的模型配置。用于验证完整链路(数据库→API→弹窗)。 -
- {/* 测试 API 连接 */} -
- - {insightTestResult && ( - - {insightTestResult.success ? : } - {insightTestResult.message} - +
+
- {/* 触发测试见解 */} -
- - {insightTriggerResult && ( - - {insightTriggerResult.success ? : } - {insightTriggerResult.message} - - )} -
+ + {insightTriggerResult && ( + + {insightTriggerResult.success ? : } + {insightTriggerResult.message} + + )}
@@ -2824,9 +2860,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { 当前显示内置默认提示词,可直接编辑修改。修改后立即生效,无需重启。可变的统计信息(触发次数、对话内容)会自动附加在用户消息里,无需在此填写。