mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-24 07:36:46 +00:00
264 lines
8.2 KiB
TypeScript
264 lines
8.2 KiB
TypeScript
import { app } from 'electron'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { createHash, randomUUID } from 'crypto'
|
|
import { ConfigService } from './config'
|
|
|
|
export type GroupSummaryTriggerType = 'auto' | 'manual'
|
|
|
|
export interface GroupSummaryTopic {
|
|
title: string
|
|
participants: string[]
|
|
keyPoints: string[]
|
|
conclusion: string
|
|
}
|
|
|
|
export interface GroupSummaryLog {
|
|
endpoint: string
|
|
model: string
|
|
temperature: number
|
|
triggerType: GroupSummaryTriggerType
|
|
periodStart: number
|
|
periodEnd: number
|
|
messageCount: number
|
|
readableMessageCount: number
|
|
systemPrompt: string
|
|
userPrompt: string
|
|
rawOutput: string
|
|
finalSummary: string
|
|
durationMs: number
|
|
createdAt: number
|
|
responseFormatJson?: boolean
|
|
responseFormatFallback?: boolean
|
|
responseFormatFallbackReason?: string
|
|
parsedTopics?: GroupSummaryTopic[]
|
|
}
|
|
|
|
export interface GroupSummaryRecord {
|
|
id: string
|
|
accountScope: string
|
|
createdAt: number
|
|
sessionId: string
|
|
displayName: string
|
|
avatarUrl?: string
|
|
triggerType: GroupSummaryTriggerType
|
|
periodStart: number
|
|
periodEnd: number
|
|
messageCount: number
|
|
readableMessageCount: number
|
|
topics: GroupSummaryTopic[]
|
|
summaryText: string
|
|
rawOutput: string
|
|
log: GroupSummaryLog
|
|
}
|
|
|
|
export interface GroupSummaryRecordSummary {
|
|
id: string
|
|
createdAt: number
|
|
sessionId: string
|
|
displayName: string
|
|
avatarUrl?: string
|
|
triggerType: GroupSummaryTriggerType
|
|
periodStart: number
|
|
periodEnd: number
|
|
messageCount: number
|
|
readableMessageCount: number
|
|
topics: GroupSummaryTopic[]
|
|
summaryText: string
|
|
}
|
|
|
|
export interface GroupSummaryRecordFilters {
|
|
sessionId?: string
|
|
startTime?: number
|
|
endTime?: number
|
|
limit?: number
|
|
offset?: number
|
|
}
|
|
|
|
export interface GroupSummaryRecordListResult {
|
|
success: boolean
|
|
records: GroupSummaryRecordSummary[]
|
|
total: number
|
|
error?: string
|
|
}
|
|
|
|
class GroupSummaryRecordService {
|
|
private readonly maxRecordsPerScope = 2000
|
|
private filePath: string | null = null
|
|
private loaded = false
|
|
private records: GroupSummaryRecord[] = []
|
|
|
|
private resolveFilePath(): string {
|
|
if (this.filePath) return this.filePath
|
|
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-group-summary-records.json')
|
|
return this.filePath
|
|
}
|
|
|
|
private ensureLoaded(): void {
|
|
if (this.loaded) return
|
|
this.loaded = true
|
|
const filePath = this.resolveFilePath()
|
|
try {
|
|
if (!fs.existsSync(filePath)) return
|
|
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
const parsed = JSON.parse(raw)
|
|
const records = Array.isArray(parsed) ? parsed : parsed?.records
|
|
if (Array.isArray(records)) {
|
|
this.records = records.filter((item) => item && typeof item === 'object') as GroupSummaryRecord[]
|
|
}
|
|
} catch {
|
|
this.records = []
|
|
}
|
|
}
|
|
|
|
private persist(): void {
|
|
try {
|
|
const filePath = this.resolveFilePath()
|
|
fs.writeFileSync(filePath, JSON.stringify({ version: 1, records: this.records }, null, 2), 'utf-8')
|
|
} catch {
|
|
// Summary generation should not fail because local record persistence failed.
|
|
}
|
|
}
|
|
|
|
private getCurrentAccountScope(): string {
|
|
const config = ConfigService.getInstance()
|
|
const myWxid = String(config.getMyWxidCleaned() || '').trim()
|
|
if (myWxid) return `wxid:${myWxid}`
|
|
|
|
const dbPath = String(config.get('dbPath') || '').trim()
|
|
if (dbPath) {
|
|
const hash = createHash('sha1').update(dbPath).digest('hex').slice(0, 16)
|
|
return `db:${hash}`
|
|
}
|
|
return 'default'
|
|
}
|
|
|
|
private toSummary(record: GroupSummaryRecord): GroupSummaryRecordSummary {
|
|
return {
|
|
id: record.id,
|
|
createdAt: record.createdAt,
|
|
sessionId: record.sessionId,
|
|
displayName: record.displayName,
|
|
avatarUrl: record.avatarUrl,
|
|
triggerType: record.triggerType,
|
|
periodStart: record.periodStart,
|
|
periodEnd: record.periodEnd,
|
|
messageCount: record.messageCount,
|
|
readableMessageCount: record.readableMessageCount,
|
|
topics: Array.isArray(record.topics) ? record.topics : [],
|
|
summaryText: record.summaryText || ''
|
|
}
|
|
}
|
|
|
|
private getScopedRecords(): GroupSummaryRecord[] {
|
|
this.ensureLoaded()
|
|
const scope = this.getCurrentAccountScope()
|
|
return this.records.filter((record) => record.accountScope === scope)
|
|
}
|
|
|
|
addRecord(input: {
|
|
sessionId: string
|
|
displayName: string
|
|
avatarUrl?: string
|
|
triggerType: GroupSummaryTriggerType
|
|
periodStart: number
|
|
periodEnd: number
|
|
messageCount: number
|
|
readableMessageCount: number
|
|
topics: GroupSummaryTopic[]
|
|
summaryText: string
|
|
rawOutput: string
|
|
log: GroupSummaryLog
|
|
}): GroupSummaryRecord {
|
|
this.ensureLoaded()
|
|
const scope = this.getCurrentAccountScope()
|
|
const record: GroupSummaryRecord = {
|
|
id: randomUUID(),
|
|
accountScope: scope,
|
|
createdAt: Date.now(),
|
|
sessionId: input.sessionId,
|
|
displayName: input.displayName,
|
|
avatarUrl: input.avatarUrl,
|
|
triggerType: input.triggerType,
|
|
periodStart: input.periodStart,
|
|
periodEnd: input.periodEnd,
|
|
messageCount: input.messageCount,
|
|
readableMessageCount: input.readableMessageCount,
|
|
topics: input.topics,
|
|
summaryText: input.summaryText,
|
|
rawOutput: input.rawOutput,
|
|
log: input.log
|
|
}
|
|
|
|
this.records.push(record)
|
|
const scopedRecords = this.records
|
|
.filter((item) => item.accountScope === scope)
|
|
.sort((a, b) => b.createdAt - a.createdAt)
|
|
const keepIds = new Set(scopedRecords.slice(0, this.maxRecordsPerScope).map((item) => item.id))
|
|
this.records = this.records.filter((item) => item.accountScope !== scope || keepIds.has(item.id))
|
|
this.persist()
|
|
return record
|
|
}
|
|
|
|
hasAutoRecord(sessionId: string, periodStart: number, periodEnd: number): boolean {
|
|
const normalizedSessionId = String(sessionId || '').trim()
|
|
if (!normalizedSessionId) return false
|
|
return this.getScopedRecords().some((record) =>
|
|
record.triggerType === 'auto' &&
|
|
record.sessionId === normalizedSessionId &&
|
|
Number(record.periodStart || 0) === periodStart &&
|
|
Number(record.periodEnd || 0) === periodEnd
|
|
)
|
|
}
|
|
|
|
listRecords(filters: GroupSummaryRecordFilters = {}): GroupSummaryRecordListResult {
|
|
try {
|
|
const sessionId = String(filters.sessionId || '').trim()
|
|
const startTime = Number(filters.startTime || 0)
|
|
const endTime = Number(filters.endTime || 0)
|
|
const offset = Math.max(0, Math.floor(Number(filters.offset || 0)))
|
|
const limit = Math.min(200, Math.max(1, Math.floor(Number(filters.limit || 100))))
|
|
|
|
const filtered = this.getScopedRecords()
|
|
.filter((record) => {
|
|
if (sessionId && record.sessionId !== sessionId) return false
|
|
const periodStart = Number(record.periodStart || 0)
|
|
const periodEnd = Number(record.periodEnd || 0)
|
|
if (startTime > 0 && periodEnd < startTime) return false
|
|
if (endTime > 0 && periodStart > endTime) return false
|
|
return true
|
|
})
|
|
.sort((a, b) => Number(b.periodStart || b.createdAt) - Number(a.periodStart || a.createdAt))
|
|
|
|
return {
|
|
success: true,
|
|
records: filtered.slice(offset, offset + limit).map((record) => this.toSummary(record)),
|
|
total: filtered.length
|
|
}
|
|
} catch (error) {
|
|
return { success: false, records: [], total: 0, error: (error as Error).message || String(error) }
|
|
}
|
|
}
|
|
|
|
getRecord(id: string): { success: boolean; record?: GroupSummaryRecord; error?: string } {
|
|
this.ensureLoaded()
|
|
const normalizedId = String(id || '').trim()
|
|
if (!normalizedId) return { success: false, error: '记录 ID 为空' }
|
|
const scope = this.getCurrentAccountScope()
|
|
const record = this.records.find((item) => item.id === normalizedId && item.accountScope === scope)
|
|
if (!record) return { success: false, error: '未找到该群聊总结记录' }
|
|
return { success: true, record }
|
|
}
|
|
|
|
clearRuntimeCache(): void {
|
|
this.loaded = false
|
|
this.records = []
|
|
this.filePath = null
|
|
}
|
|
}
|
|
|
|
export const groupSummaryRecordService = new GroupSummaryRecordService()
|