mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-27 15:11:23 +00:00
feat: Add AI User Persona
This commit is contained in:
@@ -32,6 +32,7 @@ import { httpService } from './services/httpService'
|
||||
import { messagePushService } from './services/messagePushService'
|
||||
import { insightService } from './services/insightService'
|
||||
import { insightRecordService } from './services/insightRecordService'
|
||||
import { insightProfileService } from './services/insightProfileService'
|
||||
import { groupSummaryService } from './services/groupSummaryService'
|
||||
import { normalizeWeiboCookieInput, weiboService } from './services/social/weiboService'
|
||||
import { bizService } from './services/bizService'
|
||||
@@ -1829,6 +1830,22 @@ function registerIpcHandlers() {
|
||||
return insightService.triggerSessionInsight(payload)
|
||||
})
|
||||
|
||||
ipcMain.handle('insight:listProfileStatuses', async (_, sessionIds: string[]) => {
|
||||
return insightProfileService.listProfileStatuses(Array.isArray(sessionIds) ? sessionIds : [])
|
||||
})
|
||||
|
||||
ipcMain.handle('insight:generateProfile', async (_, payload: {
|
||||
sessionId: string
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
}) => {
|
||||
return insightProfileService.generateProfile(payload)
|
||||
})
|
||||
|
||||
ipcMain.handle('insight:cancelProfile', async (_, sessionId?: string) => {
|
||||
return insightProfileService.cancelProfile(sessionId)
|
||||
})
|
||||
|
||||
ipcMain.handle('insight:generateFootprintInsight', async (_, payload: {
|
||||
rangeLabel: string
|
||||
summary: {
|
||||
|
||||
@@ -588,6 +588,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
}) => ipcRenderer.invoke('insight:triggerSessionInsight', payload),
|
||||
listProfileStatuses: (sessionIds: string[]) => ipcRenderer.invoke('insight:listProfileStatuses', sessionIds),
|
||||
generateProfile: (payload: {
|
||||
sessionId: string
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
}) => ipcRenderer.invoke('insight:generateProfile', payload),
|
||||
cancelProfile: (sessionId?: string) => ipcRenderer.invoke('insight:cancelProfile', sessionId),
|
||||
generateFootprintInsight: (payload: {
|
||||
rangeLabel: string
|
||||
summary: {
|
||||
|
||||
1001
electron/services/insightProfileService.ts
Normal file
1001
electron/services/insightProfileService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ import { chatService, ChatSession, Message } from './chatService'
|
||||
import { snsService } from './snsService'
|
||||
import { weiboService } from './social/weiboService'
|
||||
import { showNotification } from '../windows/notificationWindow'
|
||||
import { insightProfileService } from './insightProfileService'
|
||||
import {
|
||||
insightRecordService,
|
||||
type InsightRecordLog,
|
||||
@@ -385,6 +386,7 @@ class InsightService {
|
||||
this.clearTimers()
|
||||
this.clearRuntimeCache()
|
||||
this.processing = false
|
||||
insightProfileService.cancelActiveTask('AI 见解服务已停止,画像任务已取消')
|
||||
if (hadActiveFlow) {
|
||||
insightLog('INFO', '已停止')
|
||||
}
|
||||
@@ -400,6 +402,7 @@ class InsightService {
|
||||
}
|
||||
|
||||
if (normalizedKey === 'dbPath' || normalizedKey === 'decryptKey' || normalizedKey === 'myWxid') {
|
||||
insightProfileService.cancelActiveTask('数据库或账号配置已变化,画像任务已取消')
|
||||
this.clearRuntimeCache()
|
||||
}
|
||||
|
||||
@@ -409,6 +412,7 @@ class InsightService {
|
||||
handleConfigCleared(): void {
|
||||
this.clearTimers()
|
||||
this.clearRuntimeCache()
|
||||
insightProfileService.cancelActiveTask('配置已清除,画像任务已取消')
|
||||
this.processing = false
|
||||
}
|
||||
|
||||
@@ -1467,6 +1471,7 @@ ${afterText}
|
||||
|
||||
const momentsContextSection = await this.getMomentsContextSection(sessionId)
|
||||
const socialContextSection = await this.getSocialContextSection(sessionId)
|
||||
const profileContextSection = insightProfileService.getProfileContextSection(sessionId)
|
||||
|
||||
// ── 默认 system prompt(稳定内容,有利于 provider 端 prompt cache 命中)────
|
||||
const DEFAULT_SYSTEM_PROMPT = `你是用户的私人关系观察助手,名叫"见解"。你的任务是主动提供有价值的观察和建议。
|
||||
@@ -1486,6 +1491,7 @@ ${afterText}
|
||||
? `已 ${silentDays} 天未联系「${resolvedDisplayName}」。`
|
||||
: '',
|
||||
contextSection,
|
||||
profileContextSection,
|
||||
momentsContextSection,
|
||||
socialContextSection,
|
||||
'请给出你的见解(≤80字):'
|
||||
|
||||
@@ -3391,9 +3391,10 @@
|
||||
|
||||
&.insight-social-tab {
|
||||
--insight-moments-column-width: 76px;
|
||||
--insight-social-column-width: minmax(220px, 300px);
|
||||
--insight-profile-column-width: 90px;
|
||||
--insight-social-column-width: minmax(190px, 260px);
|
||||
--insight-status-column-width: 82px;
|
||||
--insight-social-list-grid: minmax(0, 1fr) var(--insight-moments-column-width) var(--insight-social-column-width) var(--insight-status-column-width);
|
||||
--insight-social-list-grid: minmax(0, 1fr) var(--insight-moments-column-width) var(--insight-profile-column-width) var(--insight-social-column-width) var(--insight-status-column-width);
|
||||
|
||||
.anti-revoke-list-header {
|
||||
grid-template-columns: var(--insight-social-list-grid);
|
||||
@@ -3405,6 +3406,12 @@
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.insight-profile-column-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.insight-social-column-title {
|
||||
min-width: 0;
|
||||
color: var(--text-tertiary);
|
||||
@@ -3435,6 +3442,63 @@
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.insight-profile-cell {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.insight-profile-status-btn {
|
||||
width: 78px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 84%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 90%, var(--bg-primary) 10%);
|
||||
color: var(--text-secondary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, opacity 0.16s ease;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
.profile-status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--text-tertiary) 86%, transparent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.ready {
|
||||
color: var(--primary);
|
||||
border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 10%, var(--bg-secondary));
|
||||
}
|
||||
|
||||
&.running {
|
||||
color: color-mix(in srgb, var(--primary) 70%, var(--text-primary) 30%);
|
||||
border-color: color-mix(in srgb, var(--primary) 30%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 9%, var(--bg-secondary));
|
||||
}
|
||||
|
||||
&.failed {
|
||||
color: color-mix(in srgb, var(--danger) 72%, var(--text-primary) 28%);
|
||||
border-color: color-mix(in srgb, var(--danger) 24%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--danger) 8%, var(--bg-secondary));
|
||||
}
|
||||
}
|
||||
|
||||
.insight-moments-toggle {
|
||||
position: relative;
|
||||
width: 18px;
|
||||
@@ -3605,6 +3669,7 @@
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
|
||||
.insight-moments-column-title,
|
||||
.insight-profile-column-title,
|
||||
.insight-social-column-title {
|
||||
display: none;
|
||||
}
|
||||
@@ -3617,12 +3682,14 @@
|
||||
}
|
||||
|
||||
.insight-moments-cell,
|
||||
.insight-profile-cell,
|
||||
.insight-social-binding-cell,
|
||||
.anti-revoke-row-status {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.insight-moments-cell {
|
||||
.insight-moments-cell,
|
||||
.insight-profile-cell {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useMemo } from 'react'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import { useChatStore } from '../stores/chatStore'
|
||||
import { useThemeStore, themes } from '../stores/themeStore'
|
||||
@@ -8,6 +9,7 @@ import { dialog } from '../services/ipc'
|
||||
import * as configService from '../services/config'
|
||||
import groupSummaryPrompt from '../../shared/groupSummaryPrompt.json'
|
||||
import type { ChatSession, ContactInfo } from '../types/models'
|
||||
import type { InsightProfileStatus } from '../types/electron'
|
||||
import {
|
||||
Eye, EyeOff, FolderSearch, FolderOpen, Search, Copy,
|
||||
RotateCcw, Trash2, Plug, Check, Sun, Moon, Monitor,
|
||||
@@ -331,6 +333,8 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [weiboBindingDrafts, setWeiboBindingDrafts] = useState<Record<string, string>>({})
|
||||
const [weiboBindingErrors, setWeiboBindingErrors] = useState<Record<string, string>>({})
|
||||
const [weiboBindingLoadingSessionId, setWeiboBindingLoadingSessionId] = useState<string | null>(null)
|
||||
const [aiInsightProfileStatuses, setAiInsightProfileStatuses] = useState<Record<string, InsightProfileStatus>>({})
|
||||
const [aiInsightProfileActiveSessionId, setAiInsightProfileActiveSessionId] = useState<string | null>(null)
|
||||
const [aiFootprintEnabled, setAiFootprintEnabled] = useState(false)
|
||||
const [aiFootprintSystemPrompt, setAiFootprintSystemPrompt] = useState('')
|
||||
const [aiGroupSummaryEnabled, setAiGroupSummaryEnabled] = useState(false)
|
||||
@@ -2861,37 +2865,41 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
showMessage('已清空主动推送过滤列表', true)
|
||||
}
|
||||
|
||||
const sessionFilterOptionMap = new Map<string, SessionFilterOption>()
|
||||
const { sessionFilterOptionMap, sessionFilterOptions } = useMemo(() => {
|
||||
const optionMap = new Map<string, SessionFilterOption>()
|
||||
|
||||
for (const session of chatSessions) {
|
||||
if (session.username.toLowerCase().includes('placeholder_foldgroup')) continue
|
||||
sessionFilterOptionMap.set(session.username, {
|
||||
username: session.username,
|
||||
displayName: session.displayName || session.username,
|
||||
avatarUrl: session.avatarUrl,
|
||||
type: getSessionFilterType(session)
|
||||
})
|
||||
}
|
||||
for (const session of chatSessions) {
|
||||
if (session.username.toLowerCase().includes('placeholder_foldgroup')) continue
|
||||
optionMap.set(session.username, {
|
||||
username: session.username,
|
||||
displayName: session.displayName || session.username,
|
||||
avatarUrl: session.avatarUrl,
|
||||
type: getSessionFilterType(session)
|
||||
})
|
||||
}
|
||||
|
||||
for (const contact of messagePushContactOptions) {
|
||||
if (!contact.username) continue
|
||||
if (contact.type !== 'friend' && contact.type !== 'group' && contact.type !== 'official' && contact.type !== 'former_friend') continue
|
||||
const existing = sessionFilterOptionMap.get(contact.username)
|
||||
sessionFilterOptionMap.set(contact.username, {
|
||||
username: contact.username,
|
||||
displayName: existing?.displayName || contact.displayName || contact.remark || contact.nickname || contact.username,
|
||||
avatarUrl: existing?.avatarUrl || contact.avatarUrl,
|
||||
type: getSessionFilterType(contact)
|
||||
})
|
||||
}
|
||||
for (const contact of messagePushContactOptions) {
|
||||
if (!contact.username) continue
|
||||
if (contact.type !== 'friend' && contact.type !== 'group' && contact.type !== 'official' && contact.type !== 'former_friend') continue
|
||||
const existing = optionMap.get(contact.username)
|
||||
optionMap.set(contact.username, {
|
||||
username: contact.username,
|
||||
displayName: existing?.displayName || contact.displayName || contact.remark || contact.nickname || contact.username,
|
||||
avatarUrl: existing?.avatarUrl || contact.avatarUrl,
|
||||
type: getSessionFilterType(contact)
|
||||
})
|
||||
}
|
||||
|
||||
const sessionFilterOptions = Array.from(sessionFilterOptionMap.values())
|
||||
.sort((a, b) => {
|
||||
const aSession = chatSessions.find(session => session.username === a.username)
|
||||
const bSession = chatSessions.find(session => session.username === b.username)
|
||||
return Number(bSession?.sortTimestamp || bSession?.lastTimestamp || 0) -
|
||||
Number(aSession?.sortTimestamp || aSession?.lastTimestamp || 0)
|
||||
})
|
||||
const options = Array.from(optionMap.values())
|
||||
.sort((a, b) => {
|
||||
const aSession = chatSessions.find(session => session.username === a.username)
|
||||
const bSession = chatSessions.find(session => session.username === b.username)
|
||||
return Number(bSession?.sortTimestamp || bSession?.lastTimestamp || 0) -
|
||||
Number(aSession?.sortTimestamp || aSession?.lastTimestamp || 0)
|
||||
})
|
||||
|
||||
return { sessionFilterOptionMap: optionMap, sessionFilterOptions: options }
|
||||
}, [chatSessions, messagePushContactOptions])
|
||||
|
||||
const getSessionFilterOptionInfo = (username: string) => {
|
||||
return sessionFilterOptionMap.get(username) || {
|
||||
@@ -3268,6 +3276,120 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
await configService.setAiInsightWeiboBindings(nextBindings)
|
||||
if (!silent) showMessage('已清除微博绑定', true)
|
||||
}
|
||||
|
||||
const refreshAiInsightProfileStatuses = async (sessionIds?: string[]) => {
|
||||
const ids = normalizeSessionIds(
|
||||
sessionIds || sessionFilterOptions
|
||||
.filter((session) => session.type === 'private')
|
||||
.map((session) => session.username)
|
||||
)
|
||||
if (ids.length === 0) {
|
||||
setAiInsightProfileStatuses({})
|
||||
setAiInsightProfileActiveSessionId(null)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const result = await window.electronAPI.insight.listProfileStatuses(ids)
|
||||
if (!result.success) return
|
||||
setAiInsightProfileStatuses(result.statuses || {})
|
||||
setAiInsightProfileActiveSessionId(result.activeTask?.sessionId || null)
|
||||
} catch (error) {
|
||||
console.warn('刷新 AI 画像状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateInsightProfile = async (session: SessionFilterOption) => {
|
||||
const sessionId = session.username
|
||||
const currentStatus = aiInsightProfileStatuses[sessionId]
|
||||
if (currentStatus?.status === 'running') {
|
||||
try {
|
||||
const result = await window.electronAPI.insight.cancelProfile(sessionId)
|
||||
showMessage(result.message || '已请求取消画像任务', result.success)
|
||||
} catch (e: any) {
|
||||
showMessage(`取消画像失败:${e?.message || String(e)}`, false)
|
||||
} finally {
|
||||
setTimeout(() => { void refreshAiInsightProfileStatuses() }, 500)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (aiInsightProfileActiveSessionId && aiInsightProfileActiveSessionId !== sessionId) return
|
||||
|
||||
setAiInsightProfileStatuses((prev) => ({
|
||||
...prev,
|
||||
[sessionId]: {
|
||||
sessionId,
|
||||
status: 'running',
|
||||
phase: '正在初始化画像...',
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
}))
|
||||
setAiInsightProfileActiveSessionId(sessionId)
|
||||
try {
|
||||
const result = await window.electronAPI.insight.generateProfile({
|
||||
sessionId,
|
||||
displayName: session.displayName || session.username,
|
||||
avatarUrl: session.avatarUrl
|
||||
})
|
||||
showMessage(result.message || (result.success ? '画像完成' : '画像失败'), result.success)
|
||||
} catch (e: any) {
|
||||
showMessage(`画像失败:${e?.message || String(e)}`, false)
|
||||
} finally {
|
||||
await refreshAiInsightProfileStatuses()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab !== 'insight') return
|
||||
const ids = sessionFilterOptions
|
||||
.filter((session) => session.type === 'private')
|
||||
.map((session) => session.username)
|
||||
if (ids.length === 0) return
|
||||
void refreshAiInsightProfileStatuses(ids)
|
||||
const timer = window.setInterval(() => {
|
||||
void refreshAiInsightProfileStatuses(ids)
|
||||
}, 2500)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [activeTab, sessionFilterOptions])
|
||||
|
||||
const getInsightProfileButtonMeta = (sessionId: string) => {
|
||||
const status = aiInsightProfileStatuses[sessionId]
|
||||
const activeOther = Boolean(aiInsightProfileActiveSessionId && aiInsightProfileActiveSessionId !== sessionId)
|
||||
if (status?.status === 'running') {
|
||||
return {
|
||||
className: 'running',
|
||||
label: '取消',
|
||||
title: status.phase || '画像生成中,点击取消',
|
||||
disabled: false,
|
||||
icon: <Loader2 size={13} style={{ animation: 'spin 1s linear infinite' }} />
|
||||
}
|
||||
}
|
||||
if (status?.status === 'ready') {
|
||||
return {
|
||||
className: 'ready',
|
||||
label: '已画像',
|
||||
title: activeOther ? '其他联系人正在画像中' : '点击以重新画像',
|
||||
disabled: activeOther,
|
||||
icon: <Check size={13} />
|
||||
}
|
||||
}
|
||||
if (status?.status === 'failed') {
|
||||
return {
|
||||
className: 'failed',
|
||||
label: '失败',
|
||||
title: activeOther ? '其他联系人正在画像中' : (status.error || '画像失败,点击重试'),
|
||||
disabled: activeOther,
|
||||
icon: <XCircle size={13} />
|
||||
}
|
||||
}
|
||||
return {
|
||||
className: 'none',
|
||||
label: '未画像',
|
||||
title: activeOther ? '其他联系人正在画像中' : '点击进行画像',
|
||||
disabled: activeOther,
|
||||
icon: <i className="profile-status-dot" aria-hidden="true" />
|
||||
}
|
||||
}
|
||||
const renderInsightTab = () => (
|
||||
<div className="tab-content">
|
||||
{/* 总开关 */}
|
||||
@@ -3842,6 +3964,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<div className="anti-revoke-list-header">
|
||||
<span>对话({filteredSessions.length})</span>
|
||||
<span className="insight-moments-column-title">朋友圈</span>
|
||||
<span className="insight-profile-column-title">画像</span>
|
||||
<span className="insight-social-column-title">社交平台(微博)</span>
|
||||
<span className="anti-revoke-status-column-title">状态</span>
|
||||
</div>
|
||||
@@ -3853,6 +3976,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const weiboDraftValue = getWeiboBindingDraftValue(session.username)
|
||||
const isBindingLoading = weiboBindingLoadingSessionId === session.username
|
||||
const weiboBindingError = weiboBindingErrors[session.username]
|
||||
const profileButtonMeta = getInsightProfileButtonMeta(session.username)
|
||||
return (
|
||||
<div
|
||||
key={session.username}
|
||||
@@ -3903,6 +4027,22 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<span className="binding-feedback muted">-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="insight-profile-cell">
|
||||
{isPrivateSession ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`insight-profile-status-btn ${profileButtonMeta.className}`}
|
||||
title={profileButtonMeta.title}
|
||||
disabled={profileButtonMeta.disabled}
|
||||
onClick={() => void handleGenerateInsightProfile(session)}
|
||||
>
|
||||
{profileButtonMeta.icon}
|
||||
<span>{profileButtonMeta.label}</span>
|
||||
</button>
|
||||
) : (
|
||||
<span className="binding-feedback muted">-</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="insight-social-binding-cell">
|
||||
{isPrivateSession ? (
|
||||
<>
|
||||
|
||||
37
src/types/electron.d.ts
vendored
37
src/types/electron.d.ts
vendored
@@ -124,6 +124,36 @@ export interface InsightRecordResult {
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type InsightProfileStatusValue = 'none' | 'ready' | 'running' | 'failed'
|
||||
|
||||
export interface InsightProfileStatus {
|
||||
sessionId: string
|
||||
status: InsightProfileStatusValue
|
||||
updatedAt?: number
|
||||
error?: string
|
||||
phase?: string
|
||||
busy?: boolean
|
||||
}
|
||||
|
||||
export interface InsightProfileStatusListResult {
|
||||
success: boolean
|
||||
statuses: Record<string, InsightProfileStatus>
|
||||
activeTask?: {
|
||||
sessionId: string
|
||||
displayName: string
|
||||
phase: string
|
||||
startedAt: number
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface InsightProfileGenerateResult {
|
||||
success: boolean
|
||||
message: string
|
||||
cancelled?: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type GroupSummaryTriggerType = 'auto' | 'manual'
|
||||
|
||||
export interface GroupSummaryTopic {
|
||||
@@ -1421,6 +1451,13 @@ export interface ElectronAPI {
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
}) => Promise<{ success: boolean; message: string; recordId?: string; insight?: string; skipped?: boolean; notificationEnabled?: boolean }>
|
||||
listProfileStatuses: (sessionIds: string[]) => Promise<InsightProfileStatusListResult>
|
||||
generateProfile: (payload: {
|
||||
sessionId: string
|
||||
displayName?: string
|
||||
avatarUrl?: string
|
||||
}) => Promise<InsightProfileGenerateResult>
|
||||
cancelProfile: (sessionId?: string) => Promise<{ success: boolean; message: string }>
|
||||
generateFootprintInsight: (payload: {
|
||||
rangeLabel: string
|
||||
summary: {
|
||||
|
||||
Reference in New Issue
Block a user