import { app, BrowserWindow } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '..')
const templatePath = path.join(root, 'resources', 'mobile_daily_report.html')
const outputDir = path.join(os.tmpdir(), 'wechatexplorer-report-fixtures')
const escapeHtml = (value) =>
String(value ?? '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
const replacePlaceholder = (html, key, value) => html.replaceAll(`{{${key}}}`, value)
const avatarSvg = (label, color) =>
`data:image/svg+xml;base64,${Buffer.from(
``
).toString('base64')}`
const localImagePath = '/Users/Wxw_/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/a969409112_d784/temp/RWTemp/2026-07/94ce24699a5a1d539c00a37ec8ace755.png'
const sampleImage = fs.existsSync(localImagePath)
? `data:image/png;base64,${fs.readFileSync(localImagePath).toString('base64')}`
: avatarSvg('图', '#dbeafe')
const avatars = {
阿宇: avatarSvg('宇', '#dcfce7'),
老周: avatarSvg('周', '#e0f2fe'),
小李: avatarSvg('李', '#fef3c7'),
'we water': avatarSvg('W', '#ede9fe'),
佩佩: avatarSvg('佩', '#fee2e2')
}
const heroNames = ['阿宇', '老周', '小李', 'we water']
const heroAvatars = heroNames
.map((name) => ``)
.join('')
const compactRequest = {
metadata: {
groupName: '技术交流',
reportDate: '2026-07-10',
dateRange: '2026-07-10 09:12-19:48',
messageCount: 382,
activeUsers: 47,
imageCount: 9,
voiceCount: 5,
stickerCount: 14,
mediaMessageCount: 28,
timeSpan: '11 h',
generatedAt: '2026-07-10 22:18',
recordNote: '基于示例数据生成的精简版日报',
footerNote: '精简版默认面向长图转发;无内容模块自动隐藏。',
heroParticipants: heroNames,
avatars,
reportMode: 'compact'
},
report: {
hero: {
headline: '接口排查和版本升级是今天主线',
summary: '白天主要围绕接口异常、升级节奏和上线安排展开,结论比争论更多,待跟进事项也比较集中。',
keyTakeaway: '大家确认本次异常更像缓存与配置问题,而不是服务端挂掉。',
pendingNote: '测试环境接口文档和回滚方案仍需补齐。',
statusLine: '今日形成 3 个结论 · 2 个待办 · 1 个问题尚未解决'
},
summaryStats: {
messageCount: 382,
activeUsers: 47,
topicCount: 4,
mediaCount: 28,
imageCount: 9,
voiceCount: 5,
stickerCount: 14,
conclusionCount: 3,
todoCount: 2,
unresolvedCount: 1
},
sectionMeta: {
hero: { enabled: true, importance: 1, confidence: 0.95, totalCount: 1, displayedCount: 1 },
topics: { enabled: true, importance: 0.98, confidence: 0.86, totalCount: 6, displayedCount: 3, hiddenCount: 3 },
importantMessages: { enabled: true, importance: 0.95, confidence: 0.84, totalCount: 6, displayedCount: 3, hiddenCount: 3 },
actions: { enabled: true, importance: 0.97, confidence: 0.81, totalCount: 5, displayedCount: 3, hiddenCount: 2 },
moments: { enabled: true, importance: 0.75, confidence: 0.75, totalCount: 3, displayedCount: 1, hiddenCount: 2 },
analytics: { enabled: true, importance: 0.8, confidence: 0.95, totalCount: 1, displayedCount: 1 },
keywords: { enabled: true, importance: 0.68, confidence: 0.92, totalCount: 16, displayedCount: 12, hiddenCount: 4 }
},
topics: [
{
title: 'GPT 接口异常排查',
timeRange: '09:20-11:05',
heat: '高',
summary: '上午先从接口超时和返回结构异常入手,几轮排查后,大家逐步把问题收敛到缓存与环境配置,而不是后端服务不可用。',
conclusions: [
{ text: '接口本身可用,异常更像本地缓存与环境变量冲突。' },
{ text: '先清缓存再复测,避免把旧响应误判成线上事故。' }
],
participants: ['阿宇', '老周', '小李'],
keywords: ['接口', '缓存', '环境变量'],
image: {
imageUrl: sampleImage,
note: '该图片引发 12 条回复。根据图片前后对话推断,这是一张帮助定位问题的截图。'
}
},
{
title: '版本升级节奏',
timeRange: '11:40-12:20',
heat: '中',
summary: '关于 React 版本是否立刻升级,讨论从“能不能升”转成“这周值不值得升”,最终倾向先补兼容性清单再动。',
conclusions: [{ text: '先列旧组件兼容清单,再决定升级窗口。' }],
participants: ['阿宇', 'we water', '佩佩'],
keywords: ['React', '升级', '兼容性']
},
{
title: '上线节奏与回滚准备',
timeRange: '15:10-16:05',
heat: '中',
summary: '下午讨论上线方案时,大家更关注回滚准备是否充分,最后把重点放在文档、监控和回滚路径补齐上。',
conclusions: [{ text: '上线前需要补一版简短回滚说明。' }],
participants: ['老周', '小李'],
keywords: ['上线', '回滚', '监控']
},
],
importantMessages: [
{ sender: '阿宇', time: '10:41', content: '先别回滚,接口能通。', note: '稳定了排查方向。' },
{ sender: '老周', time: '11:02', content: '像是缓存没清掉。', note: '把问题从服务端收敛到本地环境。' },
{ sender: '小李', time: '15:36', content: '上线前把回滚文档补一下。', note: '明确形成待办。' }
],
todos: [
{ task: '补测试环境接口文档', owner: '小李', topic: 'GPT 接口异常排查', note: '方便明天复测。' },
{ task: '整理回滚说明', owner: '老周', deadline: '今晚', topic: '上线节奏与回滚准备' }
],
unresolved: [
{ question: '缓存问题的根因是不是插件残留?', owner: '阿宇', status: '待跟进', lastDiscussedAt: '18:26', note: '目前只有推断,还没有最终证据。' }
],
quotes: [
{
messages: [
{ sender: '阿宇', content: '我以为接口炸了。' },
{ sender: '老周', content: '先别慌,先清缓存。' },
{ sender: '小李', content: '清完它居然真好了。' }
],
note: '从“要不要回滚”迅速切到“先做最小验证”,很像今天的群聊节奏。'
}
],
analytics: {
topSpeakers: [
{ name: '阿宇', count: 112 },
{ name: '老周', count: 78 },
{ name: '小李', count: 61 },
{ name: 'we water', count: 49 },
{ name: '佩佩', count: 33 }
],
activeTimeline: '09:00-09:59、10:00-10:59、15:00-15:59',
voiceLeaderboard: []
},
keywords: ['GPT', '接口', '缓存', '升级', '上线', '回滚', '监控', '兼容性', '截图', '文档', '测试环境', '复测'],
media: {
gallery: [],
voiceHighlights: [],
funBadges: []
},
resources: [],
qa: [],
storylines: [],
reversals: [],
participantChains: []
}
}
const fullRequest = JSON.parse(JSON.stringify(compactRequest))
fullRequest.metadata.recordNote = '基于示例数据生成的完整版日报'
fullRequest.metadata.reportMode = 'full'
fullRequest.report.sectionMeta = {
...fullRequest.report.sectionMeta,
resources: { enabled: true, importance: 0.58, confidence: 0.76, totalCount: 2, displayedCount: 2 },
qa: { enabled: true, importance: 0.62, confidence: 0.8, totalCount: 2, displayedCount: 2 },
storylines: { enabled: true, importance: 0.68, confidence: 0.74, totalCount: 2, displayedCount: 2 },
reversals: { enabled: true, importance: 0.55, confidence: 0.72, totalCount: 1, displayedCount: 1 },
gallery: { enabled: true, importance: 0.64, confidence: 0.82, totalCount: 2, displayedCount: 2 },
voices: { enabled: true, importance: 0.6, confidence: 0.83, totalCount: 2, displayedCount: 2 },
badges: { enabled: true, importance: 0.45, confidence: 0.68, totalCount: 2, displayedCount: 2 },
chains: { enabled: true, importance: 0.58, confidence: 0.72, totalCount: 1, displayedCount: 1 }
}
fullRequest.report.resources = [
{ title: '测试环境接口文档', description: '明天复测会直接用到的说明。', sender: '小李' },
{ title: '回滚说明草稿', description: '上线前确认回滚路径与负责人。', sender: '老周' }
]
fullRequest.report.qa = [
{ question: '今晚要不要升级 React?', answer: '先不升级,先补兼容清单。', answerer: 'we water' },
{ question: '接口是不是服务端挂了?', answer: '不是,当前更像缓存与环境问题。', answerer: '老周' }
]
fullRequest.report.storylines = [
{
title: '接口异常排查线',
stages: [
{ time: '09:20', event: '阿宇提出接口异常。' },
{ time: '09:46', event: '老周建议先清缓存。' },
{ time: '10:41', event: '确认接口本身可通。' }
],
result: '初步定位为缓存与配置冲突。'
},
{
title: '版本升级讨论线',
stages: [
{ time: '11:40', event: '开始讨论是否本周升级。' },
{ time: '12:05', event: '补充兼容性与工期顾虑。' }
],
result: '今晚不升,先补兼容清单。'
}
]
fullRequest.report.reversals = [
{ topic: '接口异常', initialView: '最初以为后端服务不稳定。', finalView: '最终判断更像缓存与配置问题。', note: '多轮验证后,排查方向明显收敛。' }
]
fullRequest.report.media = {
gallery: [
{
sender: '阿宇',
time: '09:52',
imageUrl: sampleImage,
note: '图片发出后,群里立刻围绕异常现象、返回结构和复现环境展开讨论。',
stats: '12 条后续消息 · 6 人接话',
inferenceLabel: '基于图片后的聊天上下文推断'
},
{
sender: '佩佩',
time: '17:14',
imageUrl: sampleImage,
note: '第二张图带起一轮轻松但有效的快速确认。',
stats: '5 条后续消息 · 3 人接话',
inferenceLabel: '基于图片后的聊天上下文推断'
}
],
voiceHighlights: [
{ title: '语音输出王', sender: '老周', note: '共发送 3 条语音,累计 97 秒。' },
{ title: '连续发言时刻', sender: '阿宇', note: '16:32 连发 2 条语音,共 54 秒。' }
],
funBadges: [
{ title: '高能输出王', owner: '阿宇', note: '今天一共发了 112 条消息。' },
{ title: '语音麦霸', owner: '老周', note: '语音总时长位列第一。' }
]
}
fullRequest.report.participantChains = [
{ topic: '接口异常排查', chain: ['阿宇 提出', '老周 收敛方向', '小李 验证', 'we water 定结论'], note: '比较典型的一条技术讨论链路。' }
]
fullRequest.report.analytics.voiceLeaderboard = [
{ sender: '老周', count: 3, durationSec: 97 },
{ sender: '阿宇', count: 2, durationSec: 54 }
]
async function renderRequest(request, targetBase) {
let html = await fs.readFile(templatePath, 'utf8')
const report = request.report
const metadata = request.metadata
const topicCards = report.topics
.map((topic) => {
const conclusions = (topic.conclusions || [])
.slice(0, 2)
.map((entry) => `
${escapeHtml(topic.summary)}
${conclusions ? `${escapeHtml(item.result)}
` : ''}${escapeHtml(item.note)}
${escapeHtml(item.note)}
` : ''}