mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-20 13:21:48 +08:00
feat: 私聊下新增主动性分析视图
This commit is contained in:
@@ -598,6 +598,26 @@ export function registerChatHandlers(ctx: IpcContext): void {
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取关系主动性分析数据(私聊专属)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'chat:getRelationshipStats',
|
||||
async (
|
||||
_,
|
||||
sessionId: string,
|
||||
filter?: { startTs?: number; endTs?: number },
|
||||
options?: { perseveranceThreshold?: number }
|
||||
) => {
|
||||
try {
|
||||
return await worker.getRelationshipStats(sessionId, filter, options)
|
||||
} catch (error) {
|
||||
console.error('Failed to get relationship stats:', error)
|
||||
return { months: [], members: [], totalSessions: 0, hasSessionIndex: false }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ==================== 成员管理 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
getMentionGraph,
|
||||
getLaughAnalysis,
|
||||
getClusterGraph,
|
||||
getRelationshipStats,
|
||||
searchMessages,
|
||||
deepSearchMessages,
|
||||
getMessageContext,
|
||||
@@ -174,6 +175,7 @@ const syncHandlers: Record<string, (payload: any) => any> = {
|
||||
getMentionGraph: (p) => getMentionGraph(p.sessionId, p.filter),
|
||||
getLaughAnalysis: (p) => getLaughAnalysis(p.sessionId, p.filter, p.keywords),
|
||||
getClusterGraph: (p) => getClusterGraph(p.sessionId, p.filter, p.options),
|
||||
getRelationshipStats: (p) => getRelationshipStats(p.sessionId, p.filter, p.options),
|
||||
|
||||
// AI 查询
|
||||
searchMessages: (p) => searchMessages(p.sessionId, p.keywords, p.filter, p.limit, p.offset, p.senderId),
|
||||
|
||||
@@ -17,3 +17,13 @@ export type {
|
||||
ClusterGraphLink,
|
||||
ClusterGraphOptions,
|
||||
} from './social'
|
||||
|
||||
// 关系分析(私聊主动性)
|
||||
export { getRelationshipStats } from './relationship'
|
||||
export type {
|
||||
RelationshipStats,
|
||||
RelationshipMonthStats,
|
||||
IceBreakerItem,
|
||||
ResponseLatencyMember,
|
||||
PerseveranceMember,
|
||||
} from './relationship'
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
/**
|
||||
* 关系分析模块(私聊专属)
|
||||
* 基于会话索引统计双方的主动发起、收尾、破冰、响应时延、锲而不舍行为
|
||||
*/
|
||||
|
||||
import { openDatabase, type TimeFilter } from '../../core'
|
||||
|
||||
interface MemberMonthCount {
|
||||
memberId: number
|
||||
name: string
|
||||
initiateCount: number
|
||||
closeCount: number
|
||||
}
|
||||
|
||||
export interface RelationshipMonthStats {
|
||||
month: string
|
||||
members: MemberMonthCount[]
|
||||
totalSessions: number
|
||||
}
|
||||
|
||||
export interface IceBreakerItem {
|
||||
month: string
|
||||
memberId: number
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface ResponseLatencyMember {
|
||||
memberId: number
|
||||
name: string
|
||||
avgResponseTime: number
|
||||
totalResponses: number
|
||||
}
|
||||
|
||||
export interface PerseveranceMember {
|
||||
memberId: number
|
||||
name: string
|
||||
totalDoubleTexts: number
|
||||
}
|
||||
|
||||
export interface MonthlyResponseLatency {
|
||||
month: string
|
||||
members: Array<{
|
||||
memberId: number
|
||||
name: string
|
||||
avgResponseTime: number
|
||||
responseCount: number
|
||||
}>
|
||||
}
|
||||
|
||||
export interface MonthlyPerseverance {
|
||||
month: string
|
||||
members: Array<{
|
||||
memberId: number
|
||||
name: string
|
||||
doubleTextCount: number
|
||||
}>
|
||||
}
|
||||
|
||||
export interface RelationshipOptions {
|
||||
perseveranceThreshold?: number
|
||||
}
|
||||
|
||||
export interface RelationshipStats {
|
||||
months: RelationshipMonthStats[]
|
||||
members: Array<{
|
||||
memberId: number
|
||||
name: string
|
||||
totalInitiateCount: number
|
||||
totalCloseCount: number
|
||||
}>
|
||||
totalSessions: number
|
||||
hasSessionIndex: boolean
|
||||
iceBreakers: IceBreakerItem[]
|
||||
totalIceBreaks: number
|
||||
responseLatency: ResponseLatencyMember[]
|
||||
perseverance: PerseveranceMember[]
|
||||
totalDoubleTexts: number
|
||||
monthlyResponseLatency: MonthlyResponseLatency[]
|
||||
monthlyPerseverance: MonthlyPerseverance[]
|
||||
perseveranceThreshold: number
|
||||
}
|
||||
|
||||
const ICE_BREAK_THRESHOLD = 24 * 60 * 60
|
||||
const DEFAULT_PERSEVERANCE_THRESHOLD = 300 // 5 minutes
|
||||
|
||||
export function getRelationshipStats(
|
||||
sessionId: string,
|
||||
filter?: TimeFilter,
|
||||
options?: RelationshipOptions
|
||||
): RelationshipStats {
|
||||
const perseveranceThreshold = options?.perseveranceThreshold ?? DEFAULT_PERSEVERANCE_THRESHOLD
|
||||
const db = openDatabase(sessionId)
|
||||
const emptyResult: RelationshipStats = {
|
||||
months: [],
|
||||
members: [],
|
||||
totalSessions: 0,
|
||||
hasSessionIndex: false,
|
||||
iceBreakers: [],
|
||||
totalIceBreaks: 0,
|
||||
responseLatency: [],
|
||||
perseverance: [],
|
||||
totalDoubleTexts: 0,
|
||||
monthlyResponseLatency: [],
|
||||
monthlyPerseverance: [],
|
||||
perseveranceThreshold,
|
||||
}
|
||||
|
||||
if (!db) return emptyResult
|
||||
|
||||
const sessionCount = db.prepare('SELECT COUNT(*) as count FROM chat_session').get() as { count: number } | undefined
|
||||
if (!sessionCount || sessionCount.count === 0) {
|
||||
return emptyResult
|
||||
}
|
||||
|
||||
const timeConditions: string[] = []
|
||||
const params: (number | string)[] = []
|
||||
|
||||
if (filter?.startTs !== undefined) {
|
||||
timeConditions.push('cs.start_ts >= ?')
|
||||
params.push(filter.startTs)
|
||||
}
|
||||
if (filter?.endTs !== undefined) {
|
||||
timeConditions.push('cs.start_ts <= ?')
|
||||
params.push(filter.endTs)
|
||||
}
|
||||
|
||||
const whereClause = timeConditions.length > 0 ? `WHERE ${timeConditions.join(' AND ')}` : ''
|
||||
|
||||
// ==================== Session-level ====================
|
||||
const sessionRows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
cs.id AS session_id,
|
||||
cs.start_ts,
|
||||
cs.end_ts,
|
||||
(
|
||||
SELECT m.sender_id
|
||||
FROM message_context mc
|
||||
JOIN message m ON m.id = mc.message_id
|
||||
WHERE mc.session_id = cs.id
|
||||
ORDER BY m.ts ASC, m.id ASC
|
||||
LIMIT 1
|
||||
) AS initiator_id,
|
||||
(
|
||||
SELECT m.sender_id
|
||||
FROM message_context mc
|
||||
JOIN message m ON m.id = mc.message_id
|
||||
WHERE mc.session_id = cs.id
|
||||
ORDER BY m.ts DESC, m.id DESC
|
||||
LIMIT 1
|
||||
) AS closer_id
|
||||
FROM chat_session cs
|
||||
${whereClause}
|
||||
ORDER BY cs.start_ts ASC
|
||||
`
|
||||
)
|
||||
.all(...params) as Array<{
|
||||
session_id: number
|
||||
start_ts: number
|
||||
end_ts: number
|
||||
initiator_id: number | null
|
||||
closer_id: number | null
|
||||
}>
|
||||
|
||||
const memberNames = new Map<number, string>()
|
||||
const memberRows = db
|
||||
.prepare('SELECT id, COALESCE(group_nickname, account_name, platform_id) as name FROM member')
|
||||
.all() as Array<{ id: number; name: string }>
|
||||
for (const row of memberRows) {
|
||||
memberNames.set(row.id, row.name)
|
||||
}
|
||||
|
||||
const monthMap = new Map<
|
||||
string,
|
||||
{ initiateMap: Map<number, number>; closeMap: Map<number, number>; totalSessions: number }
|
||||
>()
|
||||
const memberInitTotals = new Map<number, number>()
|
||||
const memberCloseTotals = new Map<number, number>()
|
||||
const iceBreakMap = new Map<string, Map<number, number>>()
|
||||
let totalIceBreaks = 0
|
||||
let prevEndTs: number | null = null
|
||||
|
||||
for (const row of sessionRows) {
|
||||
const month = toLocalMonth(row.start_ts)
|
||||
|
||||
if (!monthMap.has(month)) {
|
||||
monthMap.set(month, { initiateMap: new Map(), closeMap: new Map(), totalSessions: 0 })
|
||||
}
|
||||
const ms = monthMap.get(month)!
|
||||
ms.totalSessions++
|
||||
|
||||
if (row.initiator_id !== null) {
|
||||
ms.initiateMap.set(row.initiator_id, (ms.initiateMap.get(row.initiator_id) ?? 0) + 1)
|
||||
memberInitTotals.set(row.initiator_id, (memberInitTotals.get(row.initiator_id) ?? 0) + 1)
|
||||
}
|
||||
|
||||
if (row.closer_id !== null) {
|
||||
ms.closeMap.set(row.closer_id, (ms.closeMap.get(row.closer_id) ?? 0) + 1)
|
||||
memberCloseTotals.set(row.closer_id, (memberCloseTotals.get(row.closer_id) ?? 0) + 1)
|
||||
}
|
||||
|
||||
if (prevEndTs !== null && row.initiator_id !== null) {
|
||||
if (row.start_ts - prevEndTs > ICE_BREAK_THRESHOLD) {
|
||||
if (!iceBreakMap.has(month)) iceBreakMap.set(month, new Map())
|
||||
const mMap = iceBreakMap.get(month)!
|
||||
mMap.set(row.initiator_id, (mMap.get(row.initiator_id) ?? 0) + 1)
|
||||
totalIceBreaks++
|
||||
}
|
||||
}
|
||||
prevEndTs = row.end_ts
|
||||
}
|
||||
|
||||
const allMemberIds = new Set<number>()
|
||||
for (const id of memberInitTotals.keys()) allMemberIds.add(id)
|
||||
for (const id of memberCloseTotals.keys()) allMemberIds.add(id)
|
||||
|
||||
const monthKeys = Array.from(monthMap.keys()).sort((a, b) => b.localeCompare(a))
|
||||
const months: RelationshipMonthStats[] = monthKeys.map((month) => {
|
||||
const ms = monthMap.get(month)!
|
||||
const members: MemberMonthCount[] = Array.from(allMemberIds).map((memberId) => ({
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
initiateCount: ms.initiateMap.get(memberId) ?? 0,
|
||||
closeCount: ms.closeMap.get(memberId) ?? 0,
|
||||
}))
|
||||
return { month, members, totalSessions: ms.totalSessions }
|
||||
})
|
||||
|
||||
const totalSessions = sessionRows.length
|
||||
|
||||
const members = Array.from(allMemberIds)
|
||||
.map((memberId) => ({
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
totalInitiateCount: memberInitTotals.get(memberId) ?? 0,
|
||||
totalCloseCount: memberCloseTotals.get(memberId) ?? 0,
|
||||
}))
|
||||
.sort((a, b) => b.totalInitiateCount - a.totalInitiateCount)
|
||||
|
||||
const iceBreakers: IceBreakerItem[] = []
|
||||
for (const month of monthKeys) {
|
||||
const mMap = iceBreakMap.get(month)
|
||||
if (!mMap) continue
|
||||
for (const [memberId, count] of mMap) {
|
||||
iceBreakers.push({ month, memberId, name: memberNames.get(memberId) ?? `Unknown(${memberId})`, count })
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Message-level ====================
|
||||
const sessionIdList = sessionRows.map((r) => r.session_id)
|
||||
const msgStats = queryMessageLevelStats(db, sessionIdList, memberNames, perseveranceThreshold)
|
||||
|
||||
return {
|
||||
months,
|
||||
members,
|
||||
totalSessions,
|
||||
hasSessionIndex: true,
|
||||
iceBreakers,
|
||||
totalIceBreaks,
|
||||
...msgStats,
|
||||
perseveranceThreshold,
|
||||
}
|
||||
}
|
||||
|
||||
function queryMessageLevelStats(
|
||||
db: ReturnType<typeof openDatabase>,
|
||||
sessionIds: number[],
|
||||
memberNames: Map<number, string>,
|
||||
perseveranceThreshold: number
|
||||
): {
|
||||
responseLatency: ResponseLatencyMember[]
|
||||
perseverance: PerseveranceMember[]
|
||||
totalDoubleTexts: number
|
||||
monthlyResponseLatency: MonthlyResponseLatency[]
|
||||
monthlyPerseverance: MonthlyPerseverance[]
|
||||
} {
|
||||
const empty = {
|
||||
responseLatency: [],
|
||||
perseverance: [],
|
||||
totalDoubleTexts: 0,
|
||||
monthlyResponseLatency: [],
|
||||
monthlyPerseverance: [],
|
||||
}
|
||||
if (!db || sessionIds.length === 0) return empty
|
||||
|
||||
const BATCH_SIZE = 500
|
||||
|
||||
// Overall
|
||||
const responseTotals = new Map<number, { sum: number; count: number }>()
|
||||
const dtTotals = new Map<number, number>()
|
||||
|
||||
// Monthly
|
||||
const monthlyRespMap = new Map<string, Map<number, { sum: number; count: number }>>()
|
||||
const monthlyDtMap = new Map<string, Map<number, number>>()
|
||||
|
||||
for (let i = 0; i < sessionIds.length; i += BATCH_SIZE) {
|
||||
const batch = sessionIds.slice(i, i + BATCH_SIZE)
|
||||
const placeholders = batch.map(() => '?').join(',')
|
||||
|
||||
// 响应时延(overall + monthly)
|
||||
const latencyRows = db
|
||||
.prepare(
|
||||
`
|
||||
WITH msg_lag AS (
|
||||
SELECT
|
||||
m.sender_id,
|
||||
m.ts,
|
||||
LAG(m.sender_id) OVER (PARTITION BY mc.session_id ORDER BY m.ts, m.id) AS prev_sender_id,
|
||||
LAG(m.ts) OVER (PARTITION BY mc.session_id ORDER BY m.ts, m.id) AS prev_ts
|
||||
FROM message_context mc
|
||||
JOIN message m ON m.id = mc.message_id
|
||||
WHERE mc.session_id IN (${placeholders})
|
||||
AND m.type = 0
|
||||
)
|
||||
SELECT
|
||||
strftime('%Y-%m', datetime(ts, 'unixepoch', 'localtime')) AS month,
|
||||
sender_id AS responder_id,
|
||||
SUM(ts - prev_ts) AS total_time,
|
||||
COUNT(*) AS response_count
|
||||
FROM msg_lag
|
||||
WHERE prev_sender_id IS NOT NULL AND sender_id != prev_sender_id
|
||||
GROUP BY month, responder_id
|
||||
`
|
||||
)
|
||||
.all(...batch) as Array<{
|
||||
month: string
|
||||
responder_id: number
|
||||
total_time: number
|
||||
response_count: number
|
||||
}>
|
||||
|
||||
for (const row of latencyRows) {
|
||||
// overall
|
||||
const existing = responseTotals.get(row.responder_id)
|
||||
if (existing) {
|
||||
existing.sum += row.total_time
|
||||
existing.count += row.response_count
|
||||
} else {
|
||||
responseTotals.set(row.responder_id, { sum: row.total_time, count: row.response_count })
|
||||
}
|
||||
|
||||
// monthly
|
||||
if (!monthlyRespMap.has(row.month)) monthlyRespMap.set(row.month, new Map())
|
||||
const mMap = monthlyRespMap.get(row.month)!
|
||||
const mExisting = mMap.get(row.responder_id)
|
||||
if (mExisting) {
|
||||
mExisting.sum += row.total_time
|
||||
mExisting.count += row.response_count
|
||||
} else {
|
||||
mMap.set(row.responder_id, { sum: row.total_time, count: row.response_count })
|
||||
}
|
||||
}
|
||||
|
||||
// 锲而不舍(overall + monthly),带时间阈值
|
||||
const dtRows = db
|
||||
.prepare(
|
||||
`
|
||||
WITH msg_lag AS (
|
||||
SELECT
|
||||
m.sender_id,
|
||||
m.ts,
|
||||
LAG(m.sender_id) OVER (PARTITION BY mc.session_id ORDER BY m.ts, m.id) AS prev_sender_id,
|
||||
LAG(m.ts) OVER (PARTITION BY mc.session_id ORDER BY m.ts, m.id) AS prev_ts
|
||||
FROM message_context mc
|
||||
JOIN message m ON m.id = mc.message_id
|
||||
WHERE mc.session_id IN (${placeholders})
|
||||
AND m.type = 0
|
||||
)
|
||||
SELECT
|
||||
strftime('%Y-%m', datetime(ts, 'unixepoch', 'localtime')) AS month,
|
||||
sender_id,
|
||||
COUNT(*) AS double_text_count
|
||||
FROM msg_lag
|
||||
WHERE prev_sender_id IS NOT NULL
|
||||
AND sender_id = prev_sender_id
|
||||
AND (ts - prev_ts) >= ?
|
||||
GROUP BY month, sender_id
|
||||
`
|
||||
)
|
||||
.all(...batch, perseveranceThreshold) as Array<{
|
||||
month: string
|
||||
sender_id: number
|
||||
double_text_count: number
|
||||
}>
|
||||
|
||||
for (const row of dtRows) {
|
||||
// overall
|
||||
dtTotals.set(row.sender_id, (dtTotals.get(row.sender_id) ?? 0) + row.double_text_count)
|
||||
|
||||
// monthly
|
||||
if (!monthlyDtMap.has(row.month)) monthlyDtMap.set(row.month, new Map())
|
||||
const mMap = monthlyDtMap.get(row.month)!
|
||||
mMap.set(row.sender_id, (mMap.get(row.sender_id) ?? 0) + row.double_text_count)
|
||||
}
|
||||
}
|
||||
|
||||
// Build overall results
|
||||
const responseLatency: ResponseLatencyMember[] = Array.from(responseTotals.entries())
|
||||
.map(([memberId, { sum, count }]) => ({
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
avgResponseTime: Math.round(sum / count),
|
||||
totalResponses: count,
|
||||
}))
|
||||
.sort((a, b) => a.avgResponseTime - b.avgResponseTime)
|
||||
|
||||
let totalDoubleTexts = 0
|
||||
const perseverance: PerseveranceMember[] = Array.from(dtTotals.entries())
|
||||
.map(([memberId, count]) => {
|
||||
totalDoubleTexts += count
|
||||
return {
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
totalDoubleTexts: count,
|
||||
}
|
||||
})
|
||||
.sort((a, b) => b.totalDoubleTexts - a.totalDoubleTexts)
|
||||
|
||||
// Build monthly response latency
|
||||
const monthlyResponseLatency: MonthlyResponseLatency[] = Array.from(monthlyRespMap.entries())
|
||||
.sort(([a], [b]) => b.localeCompare(a))
|
||||
.map(([month, mMap]) => ({
|
||||
month,
|
||||
members: Array.from(mMap.entries())
|
||||
.map(([memberId, { sum, count }]) => ({
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
avgResponseTime: Math.round(sum / count),
|
||||
responseCount: count,
|
||||
}))
|
||||
.sort((a, b) => a.avgResponseTime - b.avgResponseTime),
|
||||
}))
|
||||
|
||||
// Build monthly perseverance
|
||||
const monthlyPerseverance: MonthlyPerseverance[] = Array.from(monthlyDtMap.entries())
|
||||
.sort(([a], [b]) => b.localeCompare(a))
|
||||
.map(([month, mMap]) => ({
|
||||
month,
|
||||
members: Array.from(mMap.entries())
|
||||
.map(([memberId, count]) => ({
|
||||
memberId,
|
||||
name: memberNames.get(memberId) ?? `Unknown(${memberId})`,
|
||||
doubleTextCount: count,
|
||||
}))
|
||||
.sort((a, b) => b.doubleTextCount - a.doubleTextCount),
|
||||
}))
|
||||
|
||||
return { responseLatency, perseverance, totalDoubleTexts, monthlyResponseLatency, monthlyPerseverance }
|
||||
}
|
||||
|
||||
function toLocalMonth(ts: number): string {
|
||||
const d = new Date(ts * 1000)
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
}
|
||||
@@ -36,11 +36,21 @@ export {
|
||||
getMentionGraph,
|
||||
getLaughAnalysis,
|
||||
getClusterGraph,
|
||||
getRelationshipStats,
|
||||
} from './advanced'
|
||||
|
||||
// 小团体图类型
|
||||
export type { ClusterGraphData, ClusterGraphNode, ClusterGraphLink, ClusterGraphOptions } from './advanced'
|
||||
|
||||
// 关系分析类型
|
||||
export type {
|
||||
RelationshipStats,
|
||||
RelationshipMonthStats,
|
||||
IceBreakerItem,
|
||||
ResponseLatencyMember,
|
||||
PerseveranceMember,
|
||||
} from './advanced'
|
||||
|
||||
// 聊天记录查询
|
||||
export {
|
||||
searchMessages,
|
||||
|
||||
@@ -329,6 +329,10 @@ export async function getClusterGraph(sessionId: string, filter?: any, options?:
|
||||
return sendToWorker('getClusterGraph', { sessionId, filter, options })
|
||||
}
|
||||
|
||||
export async function getRelationshipStats(sessionId: string, filter?: any, options?: any): Promise<any> {
|
||||
return sendToWorker('getRelationshipStats', { sessionId, filter, options })
|
||||
}
|
||||
|
||||
export async function getAllSessions(): Promise<any[]> {
|
||||
return sendToWorker('getAllSessions', {})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
MemberWithStats,
|
||||
ClusterGraphData,
|
||||
ClusterGraphOptions,
|
||||
RelationshipStats,
|
||||
} from '../../../src/types/analysis'
|
||||
import type { FileParseInfo, ConflictCheckResult, MergeParams, MergeResult } from '../../../src/types/format'
|
||||
|
||||
@@ -295,6 +296,17 @@ export const chatApi = {
|
||||
return ipcRenderer.invoke('chat:getLaughAnalysis', sessionId, filter, keywords)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取关系主动性分析数据(私聊专属)
|
||||
*/
|
||||
getRelationshipStats: (
|
||||
sessionId: string,
|
||||
filter?: { startTs?: number; endTs?: number },
|
||||
options?: { perseveranceThreshold?: number }
|
||||
): Promise<RelationshipStats> => {
|
||||
return ipcRenderer.invoke('chat:getRelationshipStats', sessionId, filter, options)
|
||||
},
|
||||
|
||||
// ==================== 成员管理 ====================
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+6
@@ -14,6 +14,7 @@ import type {
|
||||
MemberWithStats,
|
||||
ClusterGraphData,
|
||||
ClusterGraphOptions,
|
||||
RelationshipStats,
|
||||
} from '../../src/types/analysis'
|
||||
import type { FileParseInfo, ConflictCheckResult, MergeParams, MergeResult } from '../../src/types/format'
|
||||
import type { TableSchema, SQLResult } from '../../src/components/analysis/SQLLab/types'
|
||||
@@ -141,6 +142,11 @@ interface ChatApi {
|
||||
getMentionGraph: (sessionId: string, filter?: TimeFilter) => Promise<MentionGraphData>
|
||||
getClusterGraph: (sessionId: string, filter?: TimeFilter, options?: ClusterGraphOptions) => Promise<ClusterGraphData>
|
||||
getLaughAnalysis: (sessionId: string, filter?: TimeFilter, keywords?: string[]) => Promise<LaughAnalysis>
|
||||
getRelationshipStats: (
|
||||
sessionId: string,
|
||||
filter?: TimeFilter,
|
||||
options?: { perseveranceThreshold?: number }
|
||||
) => Promise<RelationshipStats>
|
||||
// 成员管理
|
||||
getMembers: (sessionId: string) => Promise<MemberWithStats[]>
|
||||
getMembersPaginated: (
|
||||
|
||||
Reference in New Issue
Block a user