From ff05dbaa3252ed10192315dea8a32cd62f238a4e Mon Sep 17 00:00:00 2001 From: ILoveBingLu Date: Thu, 29 Jan 2026 15:53:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E6=96=B0=E5=A2=9E=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E8=AE=B0=E5=BD=95=E7=8B=AC=E7=AB=8B=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=92=8C=E6=97=A5=E6=9C=9F=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增聊天记录独立窗口(ChatHistoryPage),支持在单独窗口中查看完整聊天记录 - 实现 createChatHistoryWindow 函数,支持窗口复用和主题适配 - 新增 IPC 处理器用于打开聊天记录窗口和获取单条消息 - 添加 getMessagesByDate 和 getDatesWithMessages 方法,支持按日期查询消息 - 在 preload.ts 中暴露新的 IPC 调用接口 - 新增 ChatHistoryPage.tsx 和 ChatHistoryPage.scss 组件文件 - 更新 package.json 依赖项和 package-lock.json - 更新 README.md,新增爱发电赞助支持入口 - 添加爱发电二维码图片资源 - 版本号更新至 2.1.6 - 优化聊天页面和设置页面的用户体验 - 更新类型定义和配置文件以支持新功能 --- README.md | 18 +- aifadian.jpg | Bin 0 -> 88039 bytes electron/main.ts | 110 + electron/preload.ts | 6 + electron/services/ai/aiService.ts | 212 +- electron/services/chatService.ts | 483 +- electron/services/config.ts | 12 +- electron/services/dataManagementService.ts | 24 + electron/services/exportService.ts | 1120 +-- electron/services/htmlExportGenerator.ts | 865 ++ electron/services/imageDecryptService.ts | 26 +- package-lock.json | 8502 ++++++++++++++++++++ package.json | 5 +- src/App.tsx | 11 + src/components/WhatsNewModal.tsx | 32 +- src/pages/ChatHistoryPage.scss | 124 + src/pages/ChatHistoryPage.tsx | 238 + src/pages/ChatPage.scss | 420 +- src/pages/ChatPage.tsx | 402 +- src/pages/SettingsPage.tsx | 28 +- src/services/config.ts | 19 +- src/types/electron.d.ts | 14 + src/types/models.ts | 25 + 23 files changed, 12012 insertions(+), 684 deletions(-) create mode 100644 aifadian.jpg create mode 100644 electron/services/htmlExportGenerator.ts create mode 100644 package-lock.json create mode 100644 src/pages/ChatHistoryPage.scss create mode 100644 src/pages/ChatHistoryPage.tsx diff --git a/README.md b/README.md index 82b8e8d..5380bad 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ **一款现代化的微信聊天记录查看与分析工具** [![License](https://img.shields.io/badge/license-CC--BY--NC--SA--4.0-blue.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-2.1.5-green.svg)](package.json) +[![Version](https://img.shields.io/badge/version-2.1.6-green.svg)](package.json) [![Platform](https://img.shields.io/badge/platform-Windows-0078D6.svg?logo=windows)]() [![Electron](https://img.shields.io/badge/Electron-39-47848F.svg?logo=electron)]() [![React](https://img.shields.io/badge/React-19-61DAFB.svg?logo=react)]() @@ -19,6 +19,22 @@ --- +## 💖 赞助支持 + +如果这个项目对你有帮助,欢迎通过爱发电支持我们的开发工作! + +
+ + + 爱发电 + + +你的支持是我们持续更新的动力 ❤️ + +
+ +--- + ## ✨ 功能特性 diff --git a/aifadian.jpg b/aifadian.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f2390630f893fb2b4d3ce97c0690ee9de6781bc7 GIT binary patch literal 88039 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 jlL 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 literal 0 HcmV?d00001 diff --git a/electron/main.ts b/electron/main.ts index 0aa3231..34fc9ed 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -83,6 +83,8 @@ let purchaseWindow: BrowserWindow | null = null let aiSummaryWindow: BrowserWindow | null = null // 引导窗口实例 let welcomeWindow: BrowserWindow | null = null +// 聊天记录窗口实例 +let chatHistoryWindow: BrowserWindow | null = null /** * 获取当前主题的 URL 查询参数 @@ -310,6 +312,87 @@ function createGroupAnalyticsWindow() { return groupAnalyticsWindow } +/** + * 创建独立的聊天记录窗口 + */ +function createChatHistoryWindow(sessionId: string, messageId: number) { + // 如果已存在,聚焦到现有窗口 + if (chatHistoryWindow && !chatHistoryWindow.isDestroyed()) { + if (chatHistoryWindow.isMinimized()) { + chatHistoryWindow.restore() + } + chatHistoryWindow.focus() + + // 导航到新记录 + const themeParams = getThemeQueryParams() + if (process.env.VITE_DEV_SERVER_URL) { + chatHistoryWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}?${themeParams}#/chat-history/${sessionId}/${messageId}`) + } else { + chatHistoryWindow.loadFile(join(__dirname, '../dist/index.html'), { + hash: `/chat-history/${sessionId}/${messageId}`, + query: { theme: configService?.get('theme') || 'cloud-dancer', mode: configService?.get('themeMode') || 'light' } + }) + } + return chatHistoryWindow + } + + const isDev = !!process.env.VITE_DEV_SERVER_URL + const iconPath = isDev + ? join(__dirname, '../public/icon.ico') + : join(process.resourcesPath, 'icon.ico') + + const isDark = nativeTheme.shouldUseDarkColors + + chatHistoryWindow = new BrowserWindow({ + width: 600, + height: 800, + minWidth: 400, + minHeight: 500, + icon: iconPath, + webPreferences: { + preload: join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false + }, + titleBarStyle: 'hidden', + titleBarOverlay: { + color: '#00000000', + symbolColor: isDark ? '#ffffff' : '#1a1a1a', + height: 32 + }, + show: false, + backgroundColor: isDark ? '#1A1A1A' : '#F0F0F0', + autoHideMenuBar: true + }) + + chatHistoryWindow.once('ready-to-show', () => { + chatHistoryWindow?.show() + }) + + const themeParams = getThemeQueryParams() + if (process.env.VITE_DEV_SERVER_URL) { + chatHistoryWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}?${themeParams}#/chat-history/${sessionId}/${messageId}`) + + chatHistoryWindow.webContents.on('before-input-event', (event, input) => { + if (input.key === 'F12' || (input.control && input.shift && input.key === 'I')) { + chatHistoryWindow?.webContents.openDevTools() + event.preventDefault() + } + }) + } else { + chatHistoryWindow.loadFile(join(__dirname, '../dist/index.html'), { + hash: `/chat-history/${sessionId}/${messageId}`, + query: { theme: configService?.get('theme') || 'cloud-dancer', mode: configService?.get('themeMode') || 'light' } + }) + } + + chatHistoryWindow.on('closed', () => { + chatHistoryWindow = null + }) + + return chatHistoryWindow +} + /** * 创建独立的年度报告窗口 */ @@ -1083,6 +1166,17 @@ function registerIpcHandlers() { return true }) + // 打开聊天记录窗口 + ipcMain.handle('window:openChatHistoryWindow', (_, sessionId: string, messageId: number) => { + createChatHistoryWindow(sessionId, messageId) + return true + }) + + // 获取单条消息 + ipcMain.handle('chat:getMessage', async (_, sessionId: string, localId: number) => { + return chatService.getMessageByLocalId(sessionId, localId) + }) + // 更新窗口控件主题色 ipcMain.on('window:setTitleBarOverlay', (event, options: { symbolColor: string }) => { const win = BrowserWindow.fromWebContents(event.sender) @@ -1599,6 +1693,22 @@ function registerIpcHandlers() { return result }) + ipcMain.handle('chat:getMessagesByDate', async (_, sessionId: string, targetTimestamp: number, limit?: number) => { + const result = await chatService.getMessagesByDate(sessionId, targetTimestamp, limit) + if (!result.success) { + logService?.warn('Chat', '按日期获取消息失败', { sessionId, targetTimestamp, error: result.error }) + } + return result + }) + + ipcMain.handle('chat:getDatesWithMessages', async (_, sessionId: string, year: number, month: number) => { + const result = await chatService.getDatesWithMessages(sessionId, year, month) + if (!result.success) { + logService?.warn('Chat', '获取有消息日期失败', { sessionId, year, month, error: result.error }) + } + return result + }) + // 导出相关 ipcMain.handle('export:exportSessions', async (event, sessionIds: string[], outputDir: string, options: ExportOptions) => { return exportService.exportSessions(sessionIds, outputDir, options, (progress) => { diff --git a/electron/preload.ts b/electron/preload.ts index 1ab8502..1e92121 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -73,6 +73,7 @@ contextBridge.exposeInMainWorld('electronAPI', { openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) => ipcRenderer.invoke('window:openVideoPlayerWindow', videoPath, videoWidth, videoHeight), openBrowserWindow: (url: string, title?: string) => ipcRenderer.invoke('window:openBrowserWindow', url, title), openAISummaryWindow: (sessionId: string, sessionName: string) => ipcRenderer.invoke('window:openAISummaryWindow', sessionId, sessionName), + openChatHistoryWindow: (sessionId: string, messageId: number) => ipcRenderer.invoke('window:openChatHistoryWindow', sessionId, messageId), resizeToFitVideo: (videoWidth: number, videoHeight: number) => ipcRenderer.invoke('window:resizeToFitVideo', videoWidth, videoHeight), splashReady: () => ipcRenderer.send('window:splashReady'), onSplashFadeOut: (callback: () => void) => { @@ -202,6 +203,11 @@ contextBridge.exposeInMainWorld('electronAPI', { setCurrentSession: (sessionId: string | null) => ipcRenderer.invoke('chat:setCurrentSession', sessionId), getSessionDetail: (sessionId: string) => ipcRenderer.invoke('chat:getSessionDetail', sessionId), getVoiceData: (sessionId: string, msgId: string, createTime?: number) => ipcRenderer.invoke('chat:getVoiceData', sessionId, msgId, createTime), + getMessagesByDate: (sessionId: string, targetTimestamp: number, limit?: number) => + ipcRenderer.invoke('chat:getMessagesByDate', sessionId, targetTimestamp, limit), + getMessage: (sessionId: string, localId: number) => ipcRenderer.invoke('chat:getMessage', sessionId, localId), + getDatesWithMessages: (sessionId: string, year: number, month: number) => + ipcRenderer.invoke('chat:getDatesWithMessages', sessionId, year, month), onSessionsUpdated: (callback: (sessions: any[]) => void) => { const listener = (_: any, sessions: any[]) => callback(sessions) ipcRenderer.on('chat:sessions-updated', listener) diff --git a/electron/services/ai/aiService.ts b/electron/services/ai/aiService.ts index 08434ce..1de585b 100644 --- a/electron/services/ai/aiService.ts +++ b/electron/services/ai/aiService.ts @@ -205,84 +205,172 @@ ${detailInstructions[detail as keyof typeof detailInstructions] || detailInstruc } /** - * 简单的 XML 值提取辅助函数 - */ - private extractXmlValue(xml: string, tagName: string): string { - if (!xml) return '' - const regex = new RegExp(`<${tagName}(?:\\s+[^>]*)?>([\\s\\S]*?)`, 'i') - const match = regex.exec(xml) - return match ? match[1].replace(//g, '$1').trim() : '' - } - - /** - * 格式化消息 - */ - /** - * 格式化消息 + * 格式化消息(完全依赖后端解析结果,不重复解析) */ private formatMessages(messages: Message[], contacts: Map, sessionId: string): string { - return messages.map(msg => { + const formattedLines: string[] = [] + + messages.forEach(msg => { // 获取发送者显示名称 const contact = contacts.get(msg.senderUsername || '') const sender = contact?.remark || contact?.nickName || msg.senderUsername || '未知' - // 格式化时间 - const time = new Date(msg.createTime * 1000).toLocaleString('zh-CN', { - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - }) + // 格式化时间:YYYY-MM-DD-HH:MM:SS + const date = new Date(msg.createTime * 1000) + const time = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}-${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}` + + // 调试日志:检查聊天记录消息 + if (msg.parsedContent && msg.parsedContent.includes('[聊天记录]')) { + console.log('[AIService] 发现聊天记录消息:', { + localType: msg.localType, + parsedContent: msg.parsedContent.substring(0, 100), + hasChatRecordList: !!msg.chatRecordList, + chatRecordListLength: msg.chatRecordList?.length || 0, + rawContentPreview: msg.rawContent?.substring(0, 200) + }) + } // 处理不同类型的消息 - let content = msg.parsedContent || '' + let content = '' + let messageType = '文本' - // 语音消息 (Type 34) - if (msg.localType === 34) { - // 尝试获取转写缓存 - // 注意:转写服务使用的是会话ID+创建时间作为键 + // 特殊处理1:聊天记录(有详细列表) + // 后端在 parseChatHistory() 中检查 19 并填充 chatRecordList + if (msg.chatRecordList && msg.chatRecordList.length > 0) { + messageType = '聊天记录' + const recordCount = msg.chatRecordList.length + const recordLines: string[] = [] + + // 从 parsedContent 提取标题(格式:[聊天记录] 标题) + let title = '聊天记录' + if (msg.parsedContent && msg.parsedContent.startsWith('[聊天记录]')) { + title = msg.parsedContent.replace('[聊天记录]', '').trim() || '聊天记录' + } + + recordLines.push(title) + recordLines.push(`共${recordCount}条消息:`) + + // 遍历聊天记录列表 + msg.chatRecordList.forEach((record, index) => { + const recordSender = record.sourcename || '未知' + + // 根据datatype判断消息类型 + let recordContent = '' + if (record.datatype === 1) { + // 文本消息 + recordContent = record.datadesc || record.datatitle || '' + } else if (record.datatype === 3) { + recordContent = '[图片]' + } else if (record.datatype === 34) { + recordContent = '[语音]' + } else if (record.datatype === 43) { + recordContent = '[视频]' + } else if (record.datatype === 47) { + recordContent = '[表情包]' + } else if (record.datatype === 8 || record.datatype === 49) { + // 文件消息 + recordContent = `[文件] ${record.datatitle || record.datadesc || ''}` + } else { + recordContent = record.datadesc || record.datatitle || '[媒体消息]' + } + + recordLines.push(` 第${index + 1}条 - ${recordSender}: ${recordContent}`) + }) + + content = recordLines.join('\n') + } + // 特殊处理2:语音消息 - 尝试获取转写文本 + else if (msg.localType === 34) { + messageType = '语音' const transcript = voiceTranscribeService.getCachedTranscript(sessionId, msg.createTime) - content = transcript ? `[语音] ${transcript}` : '[语音]' + content = transcript || msg.parsedContent || '[语音消息]' } - // 视频 (Type 43) - else if (msg.localType === 43) { - content = '[视频]' + // 特殊处理3:撤回消息 - 跳过 + else if (msg.localType === 10002) { + return } - // 表情包 (Type 47) - else if (msg.localType === 47) { - // 尝试从 rawContent 提取信息 - const raw = msg.rawContent || '' - // 尝试提取 cdnurl 或其他标识,但通常表情包没有有意义的文本名字 - // 这里主要区分是自定义表情还是商店表情 - const md5 = this.extractXmlValue(raw, 'md5') - content = md5 ? `[表情包]` : '[表情包]' - } - // 文件/链接 (Type 49) - else if (msg.localType === 49) { - // 提取标题和链接/描述 - const raw = msg.rawContent || '' - const title = this.extractXmlValue(raw, 'title') - const url = this.extractXmlValue(raw, 'url') - const desc = this.extractXmlValue(raw, 'des') - - let label = '[文件/链接]' - const type = this.extractXmlValue(raw, 'type') - if (type === '5') label = '[链接]' // 网页链接 - if (type === '6') label = '[文件]' // 文件 - if (type === '33' || type === '36') label = '[小程序]' - if (type === '57') label = '[引用]' // 引用产生的 AppMsg - - if (title) { - content = `${label} ${title}` - if (url && type === '5') content += ` (${url})` - else if (desc && type !== '57' && desc.length < 50) content += ` - ${desc}` + // 其他所有消息:直接使用后端解析的 parsedContent + else { + content = msg.parsedContent || '[消息]' + + // 根据 parsedContent 的前缀判断消息类型 + if (content.startsWith('[图片]')) { + messageType = '图片' + } else if (content.startsWith('[视频]')) { + messageType = '视频' + } else if (content.startsWith('[动画表情]') || content.startsWith('[表情包]')) { + messageType = '表情包' + } else if (content.startsWith('[文件]')) { + messageType = '文件' + } else if (content.startsWith('[转账]')) { + messageType = '转账' + } else if (content.startsWith('[链接]')) { + messageType = '链接' + } else if (content.startsWith('[小程序]')) { + messageType = '小程序' + } else if (content.startsWith('[聊天记录]')) { + messageType = '聊天记录' + } else if (content.startsWith('[引用消息]') || msg.localType === 244813135921) { + messageType = '引用' + } else if (content.startsWith('[位置]')) { + messageType = '位置' + } else if (content.startsWith('[名片]')) { + messageType = '名片' + } else if (content.startsWith('[通话]')) { + messageType = '通话' + } else if (msg.localType === 10000) { + messageType = '系统' + } else if (msg.localType === 1) { + messageType = '文本' } else { - content = label + // 未知类型,记录日志以便调试 + console.log(`[AIService] 未知消息类型: localType=${msg.localType}, parsedContent=${content.substring(0, 100)}`) + messageType = '未知' } } - return `[${time}] ${sender}: ${content}` - }).join('\n') + // 跳过空内容的消息(但保留图片、视频、表情包等媒体消息) + if (!content && messageType !== '图片' && messageType !== '视频' && messageType !== '表情包') { + return + } + + // 格式化输出:[消息类型] {发送者:时间 内容} + if (messageType === '文本') { + formattedLines.push(`[文本] {${sender}:${time} ${content}}`) + } else if (messageType === '转账') { + formattedLines.push(`[转账] {${sender}:${time} ${content}}`) + } else if (messageType === '链接') { + formattedLines.push(`[链接] {${sender}:${time} ${content}}`) + } else if (messageType === '文件') { + formattedLines.push(`[文件] {${sender}:${time} ${content}}`) + } else if (messageType === '语音') { + formattedLines.push(`[语音] {${sender}:${time} ${content}}`) + } else if (messageType === '图片') { + formattedLines.push(`[图片] {${sender}:${time}}`) + } else if (messageType === '视频') { + formattedLines.push(`[视频] {${sender}:${time}}`) + } else if (messageType === '表情包') { + formattedLines.push(`[表情包] {${sender}:${time}}`) + } else if (messageType === '小程序') { + formattedLines.push(`[小程序] {${sender}:${time} ${content}}`) + } else if (messageType === '聊天记录') { + formattedLines.push(`[聊天记录] {${sender}:${time} ${content}}`) + } else if (messageType === '引用') { + formattedLines.push(`[引用] {${sender}:${time} ${content}}`) + } else if (messageType === '位置') { + formattedLines.push(`[位置] {${sender}:${time} ${content}}`) + } else if (messageType === '名片') { + formattedLines.push(`[名片] {${sender}:${time} ${content}}`) + } else if (messageType === '通话') { + formattedLines.push(`[通话] {${sender}:${time} ${content}}`) + } else if (messageType === '系统') { + formattedLines.push(`[系统消息] {${time} ${content}}`) + } else { + formattedLines.push(`[${messageType}] {${sender}:${time} ${content}}`) + } + }) + + return formattedLines.join('\n') } /** diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index 470e33a..9a7f9aa 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -46,6 +46,7 @@ export interface Message { // 引用消息相关 quotedContent?: string quotedSender?: string + quotedImageMd5?: string // 图片相关 imageMd5?: string imageDatName?: string @@ -60,6 +61,30 @@ export interface Message { fileSize?: number // 文件大小(字节) fileExt?: string // 文件扩展名 fileMd5?: string // 文件 MD5 + chatRecordList?: ChatRecordItem[] // 聊天记录列表 (Type 19) +} + +export interface ChatRecordItem { + datatype: number + datadesc?: string + datatitle?: string + sourcename?: string + sourcetime?: string + sourceheadurl?: string + fileext?: string + datasize?: number + messageuuid?: string + // 媒体信息 + dataurl?: string + datathumburl?: string + datacdnurl?: string + qaeskey?: string + aeskey?: string + md5?: string + imgheight?: number + imgwidth?: number + thumbheadurl?: string + duration?: number } export interface Contact { @@ -1090,6 +1115,7 @@ class ChatService extends EventEmitter { let emojiProductId: string | undefined let quotedContent: string | undefined let quotedSender: string | undefined + let quotedImageMd5: string | undefined let imageMd5: string | undefined let imageDatName: string | undefined let videoMd5: string | undefined @@ -1115,6 +1141,7 @@ class ChatService extends EventEmitter { const quoteInfo = this.parseQuoteMessage(content) quotedContent = quoteInfo.content quotedSender = quoteInfo.sender + quotedImageMd5 = quoteInfo.imageMd5 } // 解析文件消息 (localType === 49 且 XML 中 type=6) @@ -1130,6 +1157,16 @@ class ChatService extends EventEmitter { fileMd5 = fileInfo.fileMd5 } + // 解析聊天记录 (localType === 49 且 XML 中 type=19,或者直接检查 XML type=19) + let chatRecordList: ChatRecordItem[] | undefined + if (content) { + // 先检查 XML 中是否有 type=19 + const xmlType = this.extractXmlValue(content, 'type') + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + } + const parsedContent = this.parseMessageContent(content, localType) allMessages.push({ @@ -1147,6 +1184,7 @@ class ChatService extends EventEmitter { productId: emojiProductId, quotedContent, quotedSender, + quotedImageMd5, imageMd5, imageDatName, videoMd5, @@ -1154,7 +1192,8 @@ class ChatService extends EventEmitter { fileName, fileSize, fileExt, - fileMd5 + fileMd5, + chatRecordList }) } } catch (e: any) { @@ -1210,6 +1249,286 @@ class ChatService extends EventEmitter { } } + /** + * 根据日期获取消息(用于日期跳转) + * @param sessionId 会话ID + * @param targetTimestamp 目标日期的 Unix 时间戳(秒) + * @param limit 返回消息数量 + * @returns 返回目标日期当天或之后最近的消息列表 + */ + async getMessagesByDate( + sessionId: string, + targetTimestamp: number, + limit: number = 50 + ): Promise<{ success: boolean; messages?: Message[]; targetIndex?: number; error?: string }> { + try { + if (!this.dbDir) { + const connectResult = await this.connect() + if (!connectResult.success) { + return { success: false, error: connectResult.error || '数据库未连接' } + } + } + + const myWxid = this.configService.get('myWxid') + const cleanedMyWxid = myWxid ? this.cleanAccountDirName(myWxid) : '' + + const dbTablePairs = this.findSessionTables(sessionId) + if (dbTablePairs.length === 0) { + return { success: false, error: '未找到该会话的消息表' } + } + + // 计算目标日期的开始时间戳(当天 00:00:00) + const targetDate = new Date(targetTimestamp * 1000) + targetDate.setHours(0, 0, 0, 0) + const dayStartTimestamp = Math.floor(targetDate.getTime() / 1000) + + // 从所有数据库查找目标日期或之后的第一条消息 + let allMessages: Message[] = [] + + for (const { db, tableName, dbPath } of dbTablePairs) { + try { + const hasName2IdTable = this.checkTableExists(db, 'Name2Id') + + let myRowId: number | null = null + if (myWxid && hasName2IdTable) { + const cacheKeyOriginal = `${dbPath}:${myWxid}` + const cachedRowIdOriginal = this.myRowIdCache.get(cacheKeyOriginal) + + if (cachedRowIdOriginal !== undefined) { + myRowId = cachedRowIdOriginal + } else { + const row = db.prepare('SELECT rowid FROM Name2Id WHERE user_name = ?').get(myWxid) as any + if (row?.rowid) { + myRowId = row.rowid + this.myRowIdCache.set(cacheKeyOriginal, myRowId) + } else if (cleanedMyWxid && cleanedMyWxid !== myWxid) { + const cacheKeyCleaned = `${dbPath}:${cleanedMyWxid}` + const cachedRowIdCleaned = this.myRowIdCache.get(cacheKeyCleaned) + + if (cachedRowIdCleaned !== undefined) { + myRowId = cachedRowIdCleaned + } else { + const row2 = db.prepare('SELECT rowid FROM Name2Id WHERE user_name = ?').get(cleanedMyWxid) as any + myRowId = row2?.rowid ?? null + this.myRowIdCache.set(cacheKeyCleaned, myRowId) + } + } else { + this.myRowIdCache.set(cacheKeyOriginal, null) + } + } + } + + // 查询目标日期或之后的消息,按时间升序获取 + let sql: string + let rows: any[] + + if (hasName2IdTable && myRowId !== null) { + sql = `SELECT m.*, + CASE WHEN m.real_sender_id = ? THEN 1 ELSE 0 END AS computed_is_send, + n.user_name AS sender_username + FROM ${tableName} m + LEFT JOIN Name2Id n ON m.real_sender_id = n.rowid + WHERE m.create_time >= ? + ORDER BY m.create_time ASC, m.sort_seq ASC + LIMIT ?` + rows = db.prepare(sql).all(myRowId, dayStartTimestamp, limit * 2) as any[] + } else if (hasName2IdTable) { + sql = `SELECT m.*, n.user_name AS sender_username + FROM ${tableName} m + LEFT JOIN Name2Id n ON m.real_sender_id = n.rowid + WHERE m.create_time >= ? + ORDER BY m.create_time ASC, m.sort_seq ASC + LIMIT ?` + rows = db.prepare(sql).all(dayStartTimestamp, limit * 2) as any[] + } else { + sql = `SELECT * FROM ${tableName} + WHERE create_time >= ? + ORDER BY create_time ASC, sort_seq ASC + LIMIT ?` + rows = db.prepare(sql).all(dayStartTimestamp, limit * 2) as any[] + } + + // 处理消息 + for (const row of rows) { + const content = this.decodeMessageContent(row.message_content, row.compress_content) + const localType = row.local_type || row.type || 1 + const isSend = row.computed_is_send ?? row.is_send ?? null + + let emojiCdnUrl: string | undefined + let emojiMd5: string | undefined + let emojiProductId: string | undefined + let quotedContent: string | undefined + let quotedSender: string | undefined + let quotedImageMd5: string | undefined + let imageMd5: string | undefined + let imageDatName: string | undefined + let videoMd5: string | undefined + let voiceDuration: number | undefined + + if (localType === 47 && content) { + const emojiInfo = this.parseEmojiInfo(content) + emojiCdnUrl = emojiInfo.cdnUrl + emojiMd5 = emojiInfo.md5 + emojiProductId = emojiInfo.productId + } else if (localType === 3 && content) { + const imageInfo = this.parseImageInfo(content) + imageMd5 = imageInfo.md5 + imageDatName = this.parseImageDatNameFromRow(row) + } else if (localType === 43 && content) { + videoMd5 = this.parseVideoMd5(content) + } else if (localType === 34 && content) { + voiceDuration = this.parseVoiceDuration(content) + } else if (localType === 244813135921 || (content && content.includes('57'))) { + const quoteInfo = this.parseQuoteMessage(content) + quotedContent = quoteInfo.content + quotedSender = quoteInfo.sender + quotedImageMd5 = quoteInfo.imageMd5 + } + + let fileName: string | undefined + let fileSize: number | undefined + let fileExt: string | undefined + let fileMd5: string | undefined + if (localType === 49 && content) { + const fileInfo = this.parseFileInfo(content) + fileName = fileInfo.fileName + fileSize = fileInfo.fileSize + fileExt = fileInfo.fileExt + fileMd5 = fileInfo.fileMd5 + } + + // 解析聊天记录 (检查 XML type=19) + let chatRecordList: ChatRecordItem[] | undefined + if (content) { + const xmlType = this.extractXmlValue(content, 'type') + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + } + + const parsedContent = this.parseMessageContent(content, localType) + + allMessages.push({ + localId: row.local_id || 0, + serverId: row.server_id || 0, + localType, + createTime: row.create_time || 0, + sortSeq: row.sort_seq || 0, + isSend, + senderUsername: row.sender_username || null, + parsedContent, + rawContent: content, + emojiCdnUrl, + emojiMd5, + productId: emojiProductId, + quotedContent, + quotedSender, + quotedImageMd5, + imageMd5, + imageDatName, + videoMd5, + voiceDuration, + fileName, + fileSize, + fileExt, + fileMd5, + chatRecordList + }) + } + } catch (e) { + console.error('ChatService: 按日期查询消息失败:', e) + } + } + + // 按时间升序排序 + allMessages.sort((a, b) => a.createTime - b.createTime || a.sortSeq - b.sortSeq) + + // 去重 + const seen = new Set() + allMessages = allMessages.filter(msg => { + const key = `${msg.serverId}-${msg.localId}-${msg.createTime}-${msg.sortSeq}` + if (seen.has(key)) return false + seen.add(key) + return true + }) + + // 取前 limit 条 + const messages = allMessages.slice(0, limit) + + if (messages.length === 0) { + return { success: true, messages: [], targetIndex: -1 } + } + + return { success: true, messages, targetIndex: 0 } + } catch (e) { + console.error('ChatService: 按日期获取消息失败:', e) + return { success: false, error: String(e) } + } + } + + /** + * 获取指定月份中有消息的日期列表 + * @param sessionId 会话ID + * @param year 年份 + * @param month 月份 (1-12) + * @returns 有消息的日期字符串列表 (YYYY-MM-DD) + */ + async getDatesWithMessages( + sessionId: string, + year: number, + month: number + ): Promise<{ success: boolean; dates?: string[]; error?: string }> { + try { + if (!this.dbDir) { + const connectResult = await this.connect() + if (!connectResult.success) { + return { success: false, error: connectResult.error || '数据库未连接' } + } + } + + const dbTablePairs = this.findSessionTables(sessionId) + if (dbTablePairs.length === 0) { + return { success: true, dates: [] } + } + + // 计算该月的起止时间戳 + // 注意:month 参数是 1-12,但 Date 构造函数用 0-11 + const startDate = new Date(year, month - 1, 1, 0, 0, 0) + const endDate = new Date(year, month, 0, 23, 59, 59, 999) // 下个月第0天即本月最后一天 + + const startTimestamp = Math.floor(startDate.getTime() / 1000) + const endTimestamp = Math.floor(endDate.getTime() / 1000) + + const datesSet = new Set() + + for (const { db, tableName } of dbTablePairs) { + try { + // 只查询 create_time 字段以优化性能 + const sql = `SELECT create_time FROM ${tableName} + WHERE create_time BETWEEN ? AND ?` + + const rows = db.prepare(sql).all(startTimestamp, endTimestamp) as { create_time: number }[] + + for (const row of rows) { + const date = new Date(row.create_time * 1000) + const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` + datesSet.add(dateStr) + } + } catch (e) { + console.error(`ChatService: 查询表 ${tableName} 日期失败`, e) + } + } + + // 排序 + const sortedDates = Array.from(datesSet).sort() + + return { success: true, dates: sortedDates } + } catch (e) { + console.error('ChatService: 获取有消息的日期失败:', e) + return { success: false, error: String(e) } + } + } + /** * 解析消息内容 */ @@ -1254,11 +1573,20 @@ class ChatService extends EventEmitter { const title = this.extractXmlValue(content, 'title') return title || '[引用消息]' default: - // 检查是否是 type=57 的引用消息 - if (xmlType === '57') { - const title = this.extractXmlValue(content, 'title') - return title || '[引用消息]' + // 对于未知的 localType,检查 XML type 来判断消息类型 + if (xmlType) { + // 如果有 XML type,尝试按 type 49 的逻辑解析 + if (xmlType === '2000' || xmlType === '5' || xmlType === '6' || xmlType === '19' || + xmlType === '33' || xmlType === '36' || xmlType === '49' || xmlType === '57') { + return this.parseType49(content) + } + // type=57 的引用消息 + if (xmlType === '57') { + const title = this.extractXmlValue(content, 'title') + return title || '[引用消息]' + } } + // 其他情况 if (content.length > 200) { return this.getMessageTypeLabel(localType) } @@ -1287,6 +1615,8 @@ class ChatService extends EventEmitter { return `[链接] ${title}` case '6': return `[文件] ${title}` + case '19': + return `[聊天记录] ${title}` case '33': case '36': return `[小程序] ${title}` @@ -1300,6 +1630,80 @@ class ChatService extends EventEmitter { return '[消息]' } + /** + * 解析合并转发的聊天记录 (Type 19) + */ + private parseChatHistory(content: string): ChatRecordItem[] | undefined { + try { + const type = this.extractXmlValue(content, 'type') + if (type !== '19') return undefined + + // 提取 recorditem 中的 CDATA + // CDATA 格式: + const match = /[\s\S]*?[\s\S]*?<\/recorditem>/.exec(content) + if (!match) return undefined + + const innerXml = match[1] + + const items: ChatRecordItem[] = [] + // 使用更宽松的正则匹配 dataitem + const itemRegex = /([\s\S]*?)<\/dataitem>/g + let itemMatch + + while ((itemMatch = itemRegex.exec(innerXml)) !== null) { + const attrs = itemMatch[1] + const body = itemMatch[2] + + const datatypeMatch = /datatype="(\d+)"/.exec(attrs) + const datatype = datatypeMatch ? parseInt(datatypeMatch[1]) : 0 + + const sourcename = this.extractXmlValue(body, 'sourcename') + const sourcetime = this.extractXmlValue(body, 'sourcetime') + const sourceheadurl = this.extractXmlValue(body, 'sourceheadurl') + const datadesc = this.extractXmlValue(body, 'datadesc') + const datatitle = this.extractXmlValue(body, 'datatitle') + const fileext = this.extractXmlValue(body, 'fileext') + const datasize = parseInt(this.extractXmlValue(body, 'datasize') || '0') + const messageuuid = this.extractXmlValue(body, 'messageuuid') + + // 提取媒体信息 + const dataurl = this.extractXmlValue(body, 'dataurl') + const datathumburl = this.extractXmlValue(body, 'datathumburl') || this.extractXmlValue(body, 'thumburl') + const datacdnurl = this.extractXmlValue(body, 'datacdnurl') || this.extractXmlValue(body, 'cdnurl') + const aeskey = this.extractXmlValue(body, 'aeskey') || this.extractXmlValue(body, 'qaeskey') + const md5 = this.extractXmlValue(body, 'md5') || this.extractXmlValue(body, 'datamd5') + const imgheight = parseInt(this.extractXmlValue(body, 'imgheight') || '0') + const imgwidth = parseInt(this.extractXmlValue(body, 'imgwidth') || '0') + const duration = parseInt(this.extractXmlValue(body, 'duration') || '0') + + items.push({ + datatype, + sourcename, + sourcetime, + sourceheadurl, + datadesc: this.decodeHtmlEntities(datadesc), + datatitle: this.decodeHtmlEntities(datatitle), + fileext, + datasize, + messageuuid, + dataurl: this.decodeHtmlEntities(dataurl), + datathumburl: this.decodeHtmlEntities(datathumburl), + datacdnurl: this.decodeHtmlEntities(datacdnurl), + aeskey: this.decodeHtmlEntities(aeskey), + md5, + imgheight, + imgwidth, + duration + }) + } + + return items.length > 0 ? items : undefined + } catch (e) { + console.error('ChatService: 解析聊天记录失败:', e) + return undefined + } + } + /** * 解析表情包信息 */ @@ -1495,7 +1899,7 @@ class ChatService extends EventEmitter { /** * 解析引用消息 */ - private parseQuoteMessage(content: string): { content?: string; sender?: string } { + private parseQuoteMessage(content: string): { content?: string; sender?: string; imageMd5?: string } { try { // 提取 refermsg 部分 const referMsgStart = content.indexOf('') @@ -1514,9 +1918,11 @@ class ChatService extends EventEmitter { displayName = '' } - // 提取引用内容 - const referContent = this.extractXmlValue(referMsgXml, 'content') + // 提取引用内容并解码 + let referContent = this.extractXmlValue(referMsgXml, 'content') + referContent = this.decodeHtmlEntities(referContent) const referType = this.extractXmlValue(referMsgXml, 'type') + let imageMd5: string | undefined // 根据类型渲染引用内容 let displayContent = referContent @@ -1527,6 +1933,9 @@ class ChatService extends EventEmitter { break case '3': displayContent = '[图片]' + // 尝试从引用的内容 XML 中提取图片 MD5 + const innerMd5 = this.extractXmlValue(referContent, 'md5') + imageMd5 = innerMd5 || undefined break case '34': displayContent = '[语音]' @@ -1538,7 +1947,8 @@ class ChatService extends EventEmitter { displayContent = '[动画表情]' break case '49': - displayContent = '[链接]' + const appTitle = this.extractXmlValue(referContent, 'title') + displayContent = appTitle || '[链接]' break case '42': displayContent = '[名片]' @@ -1556,7 +1966,8 @@ class ChatService extends EventEmitter { return { content: displayContent, - sender: displayName || undefined + sender: displayName || undefined, + imageMd5 } } catch { return {} @@ -3100,7 +3511,10 @@ class ChatService extends EventEmitter { /** * 获取单条消息 */ - private getMessageByLocalId(sessionId: string, localId: number): Message | null { + /** + * 获取单条消息 + */ + public async getMessageByLocalId(sessionId: string, localId: number): Promise<{ success: boolean; message?: Message; error?: string }> { const dbTablePairs = this.findSessionTables(sessionId) for (const { db, tableName } of dbTablePairs) { @@ -3111,15 +3525,22 @@ class ChatService extends EventEmitter { const localType = row.local_type || row.type || 1 return { - localId: row.local_id || 0, - serverId: row.server_id || 0, - localType, - createTime: row.create_time || 0, - sortSeq: row.sort_seq || 0, - isSend: row.is_send ?? null, - senderUsername: row.sender_username || null, - parsedContent: this.parseMessageContent(content, localType), - rawContent: content + success: true, + message: { + localId: row.local_id || 0, + serverId: row.server_id || 0, + localType, + createTime: row.create_time || 0, + sortSeq: row.sort_seq || 0, + isSend: row.is_send ?? null, + senderUsername: row.sender_username || null, + parsedContent: this.parseMessageContent(content, localType), + rawContent: content, + chatRecordList: content ? (() => { + const xmlType = this.extractXmlValue(content, 'type') + return (xmlType === '19' || localType === 49) ? this.parseChatHistory(content) : undefined + })() : undefined + } } } } catch (e) { @@ -3127,7 +3548,7 @@ class ChatService extends EventEmitter { } } - return null + return { success: false, error: 'Message not found' } } /** @@ -3144,9 +3565,9 @@ class ChatService extends EventEmitter { // 如果没有传入 createTime,尝试从数据库获取 let msgCreateTime = createTime if (!msgCreateTime) { - const msg = this.getMessageByLocalId(sessionId, localId) - if (msg) { - msgCreateTime = msg.createTime + const result = await this.getMessageByLocalId(sessionId, localId) + if (result.success && result.message) { + msgCreateTime = result.message.createTime } } @@ -3564,6 +3985,7 @@ class ChatService extends EventEmitter { let emojiProductId: string | undefined let quotedContent: string | undefined let quotedSender: string | undefined + let quotedImageMd5: string | undefined let imageMd5: string | undefined let imageDatName: string | undefined let videoMd5: string | undefined @@ -3593,10 +4015,19 @@ class ChatService extends EventEmitter { fileSize = fileInfo.fileSize fileExt = fileInfo.fileExt fileMd5 = fileInfo.fileMd5 + } + + let chatRecordList: ChatRecordItem[] | undefined + if (content) { + const xmlType = this.extractXmlValue(content, 'type') + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } } else if (localType === 244813135921 || (content && content.includes('57'))) { const quoteInfo = this.parseQuoteMessage(content) quotedContent = quoteInfo.content quotedSender = quoteInfo.sender + quotedImageMd5 = quoteInfo.imageMd5 } const parsedContent = this.parseMessageContent(content, localType) @@ -3616,6 +4047,7 @@ class ChatService extends EventEmitter { productId: emojiProductId, quotedContent, quotedSender, + quotedImageMd5, imageMd5, imageDatName, videoMd5, @@ -3623,7 +4055,8 @@ class ChatService extends EventEmitter { fileName, fileSize, fileExt, - fileMd5 + fileMd5, + chatRecordList }) } } diff --git a/electron/services/config.ts b/electron/services/config.ts index f018220..941c975 100644 --- a/electron/services/config.ts +++ b/electron/services/config.ts @@ -41,6 +41,7 @@ interface ConfigSchema { // 数据管理相关 skipIntegrityCheck: boolean + autoUpdateDatabase: boolean // 是否自动更新数据库 // AI 相关 aiCurrentProvider: string // 当前选中的提供商 @@ -75,6 +76,7 @@ const defaults: ConfigSchema = { activationData: '', logLevel: 'WARN', // 默认只记录警告和错误 skipIntegrityCheck: false, // 默认进行完整性检查 + autoUpdateDatabase: true, // 默认开启自动更新 // AI 默认配置 aiCurrentProvider: 'zhipu', aiProviderConfigs: {}, // 空对象,用户配置后填充 @@ -148,12 +150,12 @@ export class ConfigService { const oldProviderRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiProvider'").get() as { value: string } | undefined const oldApiKeyRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiApiKey'").get() as { value: string } | undefined const oldModelRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiModel'").get() as { value: string } | undefined - + if (oldProviderRow && oldApiKeyRow) { const oldProvider = JSON.parse(oldProviderRow.value) const oldApiKey = JSON.parse(oldApiKeyRow.value) const oldModel = oldModelRow ? JSON.parse(oldModelRow.value) : '' - + // 如果有旧配置且 API Key 不为空,迁移到新结构 if (oldApiKey) { const newConfigs: any = {} @@ -161,13 +163,13 @@ export class ConfigService { apiKey: oldApiKey, model: oldModel } - + this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run('aiCurrentProvider', JSON.stringify(oldProvider)) this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run('aiProviderConfigs', JSON.stringify(newConfigs)) - + // 删除旧配置 this.db.prepare("DELETE FROM config WHERE key IN ('aiProvider', 'aiApiKey', 'aiModel')").run() - + console.log('[Config] AI 配置已迁移到新结构') } } diff --git a/electron/services/dataManagementService.ts b/electron/services/dataManagementService.ts index 863a8a5..a43d820 100644 --- a/electron/services/dataManagementService.ts +++ b/electron/services/dataManagementService.ts @@ -1237,6 +1237,12 @@ class DataManagementService { * 启用自动更新(文件监听 + 定时检查) */ enableAutoUpdate(intervalSeconds: number = 30): void { + // 检查配置是否允许自动更新 + if (!this.configService.get('autoUpdateDatabase')) { + console.log('[DataManagement] 自动更新配置为关闭,跳过启动') + return + } + if (this.autoUpdateEnabled) { this.disableAutoUpdate() } @@ -1251,6 +1257,11 @@ class DataManagementService { this.autoUpdateInterval = setInterval(async () => { if (this.isUpdating) return + // 再次检查配置,以防运行时被修改 + if (!this.configService.get('autoUpdateDatabase')) { + return + } + const checkResult = await this.checkForUpdates() if (checkResult.hasUpdate) { // 通知监听器 @@ -1342,6 +1353,9 @@ class DataManagementService { this.dbWatcher = fs.watch(dbStoragePath, { recursive: true }, async (eventType, filename) => { if (!filename || this.isUpdating) return + // 检查配置 + if (!this.configService.get('autoUpdateDatabase')) return + // 只监听 .db 文件 if (!filename.toLowerCase().endsWith('.db')) return @@ -1392,6 +1406,11 @@ class DataManagementService { * 触发更新(带频率限制和队列管理) */ private triggerUpdate(): void { + // 检查配置 + if (!this.configService.get('autoUpdateDatabase')) { + return + } + // 如果正在更新,增加待处理计数 if (this.isUpdating) { this.pendingUpdateCount++ @@ -1432,6 +1451,11 @@ class DataManagementService { * @param silent 是否静默更新(不显示进度) */ async autoIncrementalUpdate(silent: boolean = false): Promise<{ success: boolean; updated: boolean; error?: string }> { + // 检查配置 + if (!this.configService.get('autoUpdateDatabase')) { + return { success: true, updated: false } + } + if (this.isUpdating) { // 如果正在更新,返回待处理状态 this.pendingUpdateCount++ diff --git a/electron/services/exportService.ts b/electron/services/exportService.ts index 54ac94d..8092b43 100644 --- a/electron/services/exportService.ts +++ b/electron/services/exportService.ts @@ -3,7 +3,9 @@ import * as path from 'path' import Database from 'better-sqlite3' import { app } from 'electron' import { ConfigService } from './config' +import { voiceTranscribeService } from './voiceTranscribeService' import * as XLSX from 'xlsx' +import { HtmlExportGenerator } from './htmlExportGenerator' // ChatLab 0.0.2 格式类型定义 interface ChatLabHeader { @@ -44,6 +46,16 @@ interface ChatLabMessage { content: string | null platformMessageId?: string replyToMessageId?: string + chatRecords?: ChatRecordItem[] // 嵌套的聊天记录 +} + +interface ChatRecordItem { + sender: string + accountName: string + timestamp: number + type: number + content: string + avatar?: string } interface ChatLabExport { @@ -331,19 +343,19 @@ class ExportService { if (contact) { const displayName = contact.remark || contact.nick_name || contact.alias || username let avatarUrl: string | undefined - + // 优先使用 URL 头像 if (hasBigHeadUrl && contact.big_head_url) { avatarUrl = contact.big_head_url } else if (hasSmallHeadUrl && contact.small_head_url) { avatarUrl = contact.small_head_url } - + // 如果没有 URL 头像,尝试从 head_image.db 获取 base64 if (!avatarUrl) { avatarUrl = await this.getAvatarFromHeadImageDb(username) } - + return { displayName, avatarUrl } } } catch { } @@ -375,18 +387,24 @@ class ExportService { * 转换微信消息类型到 ChatLab 类型 */ private convertMessageType(localType: number, content: string): number { - // 特殊处理 type 49(链接/文件/小程序等) - if (localType === 49) { - const typeMatch = /(\d+)<\/type>/i.exec(content) - if (typeMatch) { - const subType = parseInt(typeMatch[1]) - switch (subType) { - case 6: return 4 // 文件 -> FILE - case 33: - case 36: return 24 // 小程序 -> SHARE - case 57: return 25 // 引用回复 -> REPLY - default: return 7 // 链接 -> LINK - } + // 检查 XML 中的 type 标签(支持大 localType 的情况) + const xmlTypeMatch = /(\d+)<\/type>/i.exec(content) + const xmlType = xmlTypeMatch ? parseInt(xmlTypeMatch[1]) : null + + // 特殊处理 type 49 或 XML type + if (localType === 49 || xmlType) { + const subType = xmlType || 0 + switch (subType) { + case 6: return 4 // 文件 -> FILE + case 19: return 7 // 聊天记录 -> LINK (ChatLab 没有专门的聊天记录类型) + case 33: + case 36: return 24 // 小程序 -> SHARE + case 57: return 25 // 引用回复 -> REPLY + case 2000: return 99 // 转账 -> OTHER (ChatLab 没有转账类型) + case 5: + case 49: return 7 // 链接 -> LINK + default: + if (xmlType) return 7 // 有 XML type 但未知,默认为链接 } } return MESSAGE_TYPE_MAP[localType] ?? 99 // 未知类型 -> OTHER @@ -460,29 +478,86 @@ class ExportService { /** * 解析消息内容为可读文本 */ - private parseMessageContent(content: string, localType: number): string | null { + private parseMessageContent(content: string, localType: number, sessionId?: string, createTime?: number): string | null { if (!content) return null + // 检查 XML 中的 type 标签(支持大 localType 的情况) + const xmlTypeMatch = /(\d+)<\/type>/i.exec(content) + const xmlType = xmlTypeMatch ? xmlTypeMatch[1] : null + switch (localType) { case 1: // 文本 return this.stripSenderPrefix(content) case 3: return '[图片]' - case 34: return '[语音消息]' + case 34: { + // 语音消息 - 尝试获取转写文字 + if (sessionId && createTime) { + const transcript = voiceTranscribeService.getCachedTranscript(sessionId, createTime) + if (transcript) { + return `[语音消息] ${transcript}` + } + } + return '[语音消息]' + } case 42: return '[名片]' case 43: return '[视频]' case 47: return '[动画表情]' case 48: return '[位置]' case 49: { const title = this.extractXmlValue(content, 'title') - return title || '[链接]' + const type = this.extractXmlValue(content, 'type') + + // 转账消息特殊处理 + if (type === '2000') { + const feedesc = this.extractXmlValue(content, 'feedesc') + const payMemo = this.extractXmlValue(content, 'pay_memo') + if (feedesc) { + return payMemo ? `[转账] ${feedesc} ${payMemo}` : `[转账] ${feedesc}` + } + return '[转账]' + } + + if (type === '6') return title ? `[文件] ${title}` : '[文件]' + if (type === '19') return title ? `[聊天记录] ${title}` : '[聊天记录]' + if (type === '33' || type === '36') return title ? `[小程序] ${title}` : '[小程序]' + if (type === '57') return title || '[引用消息]' + if (type === '5' || type === '49') return title ? `[链接] ${title}` : '[链接]' + return title ? `[链接] ${title}` : '[链接]' } case 50: return '[通话]' case 10000: return this.cleanSystemMessage(content) + case 244813135921: { + // 引用消息 + const title = this.extractXmlValue(content, 'title') + return title || '[引用消息]' + } default: - if (content.includes('57')) { + // 对于未知的 localType,检查 XML type 来判断消息类型 + if (xmlType) { const title = this.extractXmlValue(content, 'title') - return title || '[引用消息]' + + // 转账消息 + if (xmlType === '2000') { + const feedesc = this.extractXmlValue(content, 'feedesc') + const payMemo = this.extractXmlValue(content, 'pay_memo') + if (feedesc) { + return payMemo ? `[转账] ${feedesc} ${payMemo}` : `[转账] ${feedesc}` + } + return '[转账]' + } + + // 其他类型 + if (xmlType === '6') return title ? `[文件] ${title}` : '[文件]' + if (xmlType === '19') return title ? `[聊天记录] ${title}` : '[聊天记录]' + if (xmlType === '33' || xmlType === '36') return title ? `[小程序] ${title}` : '[小程序]' + if (xmlType === '57') return title || '[引用消息]' + if (xmlType === '5' || xmlType === '49') return title ? `[链接] ${title}` : '[链接]' + + // 有 title 就返回 title + if (title) return title } + + // 最后尝试提取文本内容 return this.stripSenderPrefix(content) || null } } @@ -619,6 +694,13 @@ class ExportService { } } + // 检查是否是聊天记录消息(type=19) + const xmlType = this.extractXmlValue(content, 'type') + let chatRecordList: any[] | undefined + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + allMessages.push({ createTime, localType, @@ -627,7 +709,8 @@ class ExportService { isSend, platformMessageId, replyToMessageId, - groupNickname + groupNickname, + chatRecordList }) // 收集成员信息 @@ -662,21 +745,113 @@ class ExportService { }) // 构建 ChatLab 格式消息 - const chatLabMessages: ChatLabMessage[] = allMessages.map(msg => { + const chatLabMessages: ChatLabMessage[] = [] + + for (const msg of allMessages) { const memberInfo = memberSet.get(msg.senderUsername) || { platformId: msg.senderUsername, accountName: msg.senderUsername } const message: ChatLabMessage = { sender: msg.senderUsername, accountName: memberInfo.accountName, timestamp: msg.createTime, type: this.convertMessageType(msg.localType, msg.content), - content: this.parseMessageContent(msg.content, msg.localType) + content: this.parseMessageContent(msg.content, msg.localType, sessionId, msg.createTime) } + // 添加可选字段 if (msg.groupNickname) message.groupNickname = msg.groupNickname if (msg.platformMessageId) message.platformMessageId = msg.platformMessageId if (msg.replyToMessageId) message.replyToMessageId = msg.replyToMessageId - return message - }) + + // 如果有聊天记录,添加为嵌套字段 + if (msg.chatRecordList && msg.chatRecordList.length > 0) { + const chatRecords: ChatRecordItem[] = [] + + for (const record of msg.chatRecordList) { + // 解析时间戳 (格式: "YYYY-MM-DD HH:MM:SS") + let recordTimestamp = msg.createTime + if (record.sourcetime) { + try { + const timeParts = record.sourcetime.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) + if (timeParts) { + const date = new Date( + parseInt(timeParts[1]), + parseInt(timeParts[2]) - 1, + parseInt(timeParts[3]), + parseInt(timeParts[4]), + parseInt(timeParts[5]), + parseInt(timeParts[6]) + ) + recordTimestamp = Math.floor(date.getTime() / 1000) + } + } catch (e) { + console.error('解析聊天记录时间失败:', e) + } + } + + // 转换消息类型 + let recordType = 0 // TEXT + let recordContent = record.datadesc || record.datatitle || '' + + switch (record.datatype) { + case 1: + recordType = 0 // TEXT + break + case 3: + recordType = 1 // IMAGE + recordContent = '[图片]' + break + case 8: + case 49: + recordType = 4 // FILE + recordContent = record.datatitle ? `[文件] ${record.datatitle}` : '[文件]' + break + case 34: + recordType = 2 // VOICE + recordContent = '[语音消息]' + break + case 43: + recordType = 3 // VIDEO + recordContent = '[视频]' + break + case 47: + recordType = 5 // EMOJI + recordContent = '[动画表情]' + break + default: + recordType = 0 + recordContent = record.datadesc || record.datatitle || '[消息]' + } + + const chatRecord: ChatRecordItem = { + sender: record.sourcename || 'unknown', + accountName: record.sourcename || 'unknown', + timestamp: recordTimestamp, + type: recordType, + content: recordContent + } + + // 添加头像(如果启用导出头像) + if (options.exportAvatars && record.sourceheadurl) { + chatRecord.avatar = record.sourceheadurl + } + + chatRecords.push(chatRecord) + + // 添加成员信息 + if (record.sourcename && !memberSet.has(record.sourcename)) { + memberSet.set(record.sourcename, { + platformId: record.sourcename, + accountName: record.sourcename, + ...(options.exportAvatars && record.sourceheadurl && { avatar: record.sourceheadurl }) + }) + } + } + + message.chatRecords = chatRecords + } + + chatLabMessages.push(message) + } // 构建 meta const meta: ChatLabMeta = { @@ -764,6 +939,158 @@ class ExportService { return undefined } + /** + * 解析合并转发的聊天记录 (Type 19) + */ + private parseChatHistory(content: string): any[] | undefined { + try { + const type = this.extractXmlValue(content, 'type') + if (type !== '19') return undefined + + // 提取 recorditem 中的 CDATA + const match = /[\s\S]*?[\s\S]*?<\/recorditem>/.exec(content) + if (!match) return undefined + + const innerXml = match[1] + const items: any[] = [] + const itemRegex = /([\s\S]*?)<\/dataitem>/g + let itemMatch + + while ((itemMatch = itemRegex.exec(innerXml)) !== null) { + const attrs = itemMatch[1] + const body = itemMatch[2] + + const datatypeMatch = /datatype="(\d+)"/.exec(attrs) + const datatype = datatypeMatch ? parseInt(datatypeMatch[1]) : 0 + + const sourcename = this.extractXmlValue(body, 'sourcename') + const sourcetime = this.extractXmlValue(body, 'sourcetime') + const sourceheadurl = this.extractXmlValue(body, 'sourceheadurl') + const datadesc = this.extractXmlValue(body, 'datadesc') + const datatitle = this.extractXmlValue(body, 'datatitle') + const fileext = this.extractXmlValue(body, 'fileext') + const datasize = parseInt(this.extractXmlValue(body, 'datasize') || '0') + + items.push({ + datatype, + sourcename, + sourcetime, + sourceheadurl, + datadesc: this.decodeHtmlEntities(datadesc), + datatitle: this.decodeHtmlEntities(datatitle), + fileext, + datasize + }) + } + + return items.length > 0 ? items : undefined + } catch (e) { + console.error('ExportService: 解析聊天记录失败:', e) + return undefined + } + } + + /** + * 解码 HTML 实体 + */ + private decodeHtmlEntities(text: string): string { + if (!text) return '' + return text + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + } + + /** + * 格式化聊天记录为 JSON 导出格式 + */ + private formatChatRecordsForJson(chatRecordList: any[], options: ExportOptions): any[] { + return chatRecordList.map(record => { + // 解析时间戳 + let timestamp = 0 + if (record.sourcetime) { + try { + const timeParts = record.sourcetime.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) + if (timeParts) { + const date = new Date( + parseInt(timeParts[1]), + parseInt(timeParts[2]) - 1, + parseInt(timeParts[3]), + parseInt(timeParts[4]), + parseInt(timeParts[5]), + parseInt(timeParts[6]) + ) + timestamp = Math.floor(date.getTime() / 1000) + } + } catch (e) { + console.error('解析聊天记录时间失败:', e) + } + } + + // 转换消息类型名称 + let typeName = '文本消息' + let content = record.datadesc || record.datatitle || '' + + switch (record.datatype) { + case 1: + typeName = '文本消息' + break + case 3: + typeName = '图片消息' + content = '[图片]' + break + case 8: + case 49: + typeName = '文件消息' + content = record.datatitle ? `[文件] ${record.datatitle}` : '[文件]' + break + case 34: + typeName = '语音消息' + content = '[语音消息]' + break + case 43: + typeName = '视频消息' + content = '[视频]' + break + case 47: + typeName = '动画表情' + content = '[动画表情]' + break + default: + typeName = '其他消息' + content = record.datadesc || record.datatitle || '[消息]' + } + + const chatRecord: any = { + sender: record.sourcename || 'unknown', + senderDisplayName: record.sourcename || 'unknown', + timestamp, + formattedTime: timestamp > 0 ? this.formatTimestamp(timestamp) : record.sourcetime, + type: typeName, + datatype: record.datatype, + content + } + + // 添加头像 + if (options.exportAvatars && record.sourceheadurl) { + chatRecord.senderAvatar = record.sourceheadurl + } + + // 添加文件信息 + if (record.fileext) { + chatRecord.fileExt = record.fileext + } + if (record.datasize > 0) { + chatRecord.fileSize = record.datasize + } + + return chatRecord + }) + } + /** * 从 extra_buffer 中提取手机号 * 微信的 extra_buffer 是 protobuf 格式的二进制数据 @@ -904,7 +1231,25 @@ class ExportService { /** * 获取消息类型名称 */ - private getMessageTypeName(localType: number): string { + private getMessageTypeName(localType: number, content?: string): string { + // 检查 XML 中的 type 标签(支持大 localType 的情况) + if (content) { + const xmlTypeMatch = /(\d+)<\/type>/i.exec(content) + const xmlType = xmlTypeMatch ? xmlTypeMatch[1] : null + + if (xmlType) { + switch (xmlType) { + case '2000': return '转账消息' + case '5': return '链接消息' + case '6': return '文件消息' + case '19': return '聊天记录' + case '33': + case '36': return '小程序消息' + case '57': return '引用消息' + } + } + } + const typeNames: Record = { 1: '文本消息', 3: '图片消息', @@ -1034,21 +1379,30 @@ class ExportService { // 提取群昵称 const groupNickname = isGroup ? this.extractGroupNickname(content, actualSender) : undefined + // 检查是否是聊天记录消息(type=19) + const xmlType = this.extractXmlValue(content, 'type') + let chatRecordList: any[] | undefined + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + allMessages.push({ localId: row.local_id || allMessages.length + 1, platformMessageId, createTime, formattedTime: this.formatTimestamp(createTime), - type: this.getMessageTypeName(localType), + type: this.getMessageTypeName(localType, content), localType, chatLabType: this.convertMessageType(localType, content), - content: this.parseMessageContent(content, localType), + content: this.parseMessageContent(content, localType, sessionId, createTime), + rawContent: content, // 保留原始内容 isSend: isSend ? 1 : 0, senderUsername: actualSender, senderDisplayName: senderInfo.displayName, ...(groupNickname && { groupNickname }), ...(replyToMessageId && { replyToMessageId }), ...(options.exportAvatars && senderInfo.avatarUrl && { senderAvatar: senderInfo.avatarUrl }), + ...(chatRecordList && { chatRecords: this.formatChatRecordsForJson(chatRecordList, options) }), source }) @@ -1176,22 +1530,31 @@ class ExportService { const content = this.decodeMessageContent(row.message_content, row.compress_content) const localType = row.local_type || row.type || 1 const senderUsername = row.sender_username || '' - + // 判断是否是自己发送的消息 - const isSend = row.is_send === 1 || - senderUsername === cleanedMyWxid || - senderUsername === fullMyWxid + const isSend = row.is_send === 1 || + senderUsername === cleanedMyWxid || + senderUsername === fullMyWxid const actualSender = isSend ? cleanedMyWxid : (senderUsername || sessionId) const senderInfo = await this.getContactInfo(actualSender) + // 检查是否是聊天记录消息(type=19) + const xmlType = this.extractXmlValue(content, 'type') + let chatRecordList: any[] | undefined + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + allMessages.push({ createTime, talker: actualSender, type: localType, content, senderName: senderInfo.displayName, - isSend // 保存发送方向 + senderAvatar: options.exportAvatars ? senderInfo.avatarUrl : undefined, + isSend, + chatRecordList }) } } catch (e) { @@ -1208,24 +1571,24 @@ class ExportService { // 准备 Excel 数据 const excelData: any[] = [] - + for (let index = 0; index < allMessages.length; index++) { const msg = allMessages[index] - const msgType = this.getMessageTypeText(msg.type) + const msgType = this.getMessageTypeName(msg.type, msg.content) const time = new Date(msg.createTime * 1000) - - // 获取发送者完整信息 - const senderInfo = await this.getContactInfo(msg.talker) - + + // 获取消息内容(使用统一的解析方法) + const messageContent = this.parseMessageContent(msg.content, msg.type, sessionId, msg.createTime) + const row: any = { '序号': index + 1, - '时间': time.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' + '时间': time.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' }), '日期': time.toLocaleDateString('zh-CN'), '时刻': time.toLocaleTimeString('zh-CN'), @@ -1233,16 +1596,26 @@ class ExportService { '发送者': msg.senderName, '微信ID': msg.talker, '消息类型': msgType, - '消息内容': this.getMessageContent(msg), + '消息内容': messageContent || '', '原始类型代码': msg.type, '时间戳': msg.createTime } - + // 只有勾选导出头像时才添加头像链接列 - if (options.exportAvatars) { - row['头像链接'] = senderInfo.avatarUrl || '' + if (options.exportAvatars && msg.senderAvatar) { + row['头像链接'] = msg.senderAvatar } - + + // 如果有聊天记录,添加聊天记录详情列 + if (msg.chatRecordList && msg.chatRecordList.length > 0) { + const recordDetails = msg.chatRecordList.map((record: any, idx: number) => { + const recordType = this.getChatRecordTypeName(record.datatype) + const recordContent = this.getChatRecordContent(record) + return `${idx + 1}. [${record.sourcename}] ${record.sourcetime} ${recordType}: ${recordContent}` + }).join('\n') + row['聊天记录详情'] = recordDetails + } + excelData.push(row) } @@ -1250,7 +1623,7 @@ class ExportService { const wb = XLSX.utils.book_new() const ws = XLSX.utils.json_to_sheet(excelData) - // 设置列宽(根据是否导出头像动态调整) + // 设置列宽(根据是否导出头像和聊天记录动态调整) const colWidths: any[] = [ { wch: 6 }, // 序号 { wch: 20 }, // 时间 @@ -1259,16 +1632,22 @@ class ExportService { { wch: 6 }, // 星期 { wch: 15 }, // 发送者 { wch: 25 }, // 微信ID - { wch: 10 }, // 消息类型 + { wch: 12 }, // 消息类型 { wch: 50 }, // 消息内容 { wch: 8 }, // 原始类型代码 { wch: 12 } // 时间戳 ] - + if (options.exportAvatars) { colWidths.push({ wch: 50 }) // 头像链接 } - + + // 检查是否有聊天记录消息 + const hasChatRecords = allMessages.some(msg => msg.chatRecordList && msg.chatRecordList.length > 0) + if (hasChatRecords) { + colWidths.push({ wch: 80 }) // 聊天记录详情 + } + ws['!cols'] = colWidths // 添加工作表(工作表名称最多31个字符,且不能包含特殊字符) @@ -1294,7 +1673,7 @@ class ExportService { } /** - * 导出单个会话为 HTML 格式 + * 导出单个会话为 HTML 格式(数据内嵌版本) */ async exportSessionToHtml( sessionId: string, @@ -1307,8 +1686,9 @@ class ExportService { } const sessionInfo = await this.getContactInfo(sessionId) - const cleanedMyWxid = (this.configService.get('myWxid') || '').replace(/^wxid_/, '') - const fullMyWxid = `wxid_${cleanedMyWxid}` + const myWxid = this.configService.get('myWxid') || '' + const cleanedMyWxid = this.cleanAccountDirName(myWxid) + const isGroup = sessionId.includes('@chatroom') // 查找消息数据库和表 const dbTablePairs = this.findSessionTables(sessionId) @@ -1318,6 +1698,7 @@ class ExportService { // 收集所有消息 const allMessages: any[] = [] + const memberSet = new Map() for (const { db, tableName } of dbTablePairs) { try { @@ -1349,24 +1730,50 @@ class ExportService { const content = this.decodeMessageContent(row.message_content, row.compress_content) const localType = row.local_type || row.type || 1 const senderUsername = row.sender_username || '' - - // 判断是否是自己发送的消息 - const isSend = row.is_send === 1 || - senderUsername === cleanedMyWxid || - senderUsername === fullMyWxid + const isSend = row.is_send === 1 || senderUsername === cleanedMyWxid const actualSender = isSend ? cleanedMyWxid : (senderUsername || sessionId) const senderInfo = await this.getContactInfo(actualSender) + // 检查是否是聊天记录消息 + const xmlType = this.extractXmlValue(content, 'type') + let chatRecordList: any[] | undefined + if (xmlType === '19' || localType === 49) { + chatRecordList = this.parseChatHistory(content) + } + allMessages.push({ - createTime, - talker: actualSender, - type: localType, - content, + timestamp: createTime, + sender: actualSender, senderName: senderInfo.displayName, - avatarUrl: options.exportAvatars ? senderInfo.avatarUrl : undefined, - isSend + type: localType, + content: this.parseMessageContent(content, localType, sessionId, createTime), + rawContent: content, + isSend, + chatRecords: chatRecordList ? this.formatChatRecordsForJson(chatRecordList, options) : undefined }) + + // 收集成员信息 + if (!memberSet.has(actualSender)) { + memberSet.set(actualSender, { + id: actualSender, + name: senderInfo.displayName, + avatar: options.exportAvatars ? senderInfo.avatarUrl : undefined + }) + } + + // 收集聊天记录中的成员 + if (chatRecordList) { + for (const record of chatRecordList) { + if (record.sourcename && !memberSet.has(record.sourcename)) { + memberSet.set(record.sourcename, { + id: record.sourcename, + name: record.sourcename, + avatar: options.exportAvatars ? record.sourceheadurl : undefined + }) + } + } + } } } catch (e) { console.error(`读取消息表 ${tableName} 失败:`, e) @@ -1378,15 +1785,47 @@ class ExportService { } // 按时间排序 - allMessages.sort((a, b) => a.createTime - b.createTime) + allMessages.sort((a, b) => a.timestamp - b.timestamp) - // 生成 HTML - const html = this.generateHtmlContent(sessionInfo, allMessages, options) + // 准备导出数据 + const exportData = { + meta: { + sessionId, + sessionName: sessionInfo.displayName, + isGroup, + exportTime: Date.now(), + messageCount: allMessages.length, + dateRange: options.dateRange ? { + start: options.dateRange.start, + end: options.dateRange.end + } : null + }, + members: Array.from(memberSet.values()), + messages: allMessages + } - // 写入文件 - fs.writeFileSync(outputPath, html, 'utf-8') + // 创建导出目录 + const exportDir = path.dirname(outputPath) + const baseName = path.basename(outputPath, '.html') + const exportFolder = path.join(exportDir, baseName) + + // 如果目录不存在则创建 + if (!fs.existsSync(exportFolder)) { + fs.mkdirSync(exportFolder, { recursive: true }) + } - return { success: true } + // 生成并写入各个文件 + const htmlPath = path.join(exportFolder, 'index.html') + const cssPath = path.join(exportFolder, 'styles.css') + const jsPath = path.join(exportFolder, 'app.js') + const dataPath = path.join(exportFolder, 'data.js') + + fs.writeFileSync(htmlPath, HtmlExportGenerator.generateHtmlWithData(exportData), 'utf-8') + fs.writeFileSync(cssPath, HtmlExportGenerator.generateCss(), 'utf-8') + fs.writeFileSync(jsPath, HtmlExportGenerator.generateJs(), 'utf-8') + fs.writeFileSync(dataPath, HtmlExportGenerator.generateDataJs(exportData), 'utf-8') + + return { success: true, outputPath: htmlPath } } catch (e) { console.error('ExportService: HTML 导出失败:', e) return { success: false, error: String(e) } @@ -1394,476 +1833,42 @@ class ExportService { } /** - * 生成 HTML 内容 + * 获取聊天记录消息的类型名称 */ - private generateHtmlContent(sessionInfo: any, messages: any[], options: ExportOptions): string { - const title = `${sessionInfo.displayName} - 聊天记录` - const totalMessages = messages.length - const dateRange = options.dateRange - ? `${new Date(options.dateRange.start * 1000).toLocaleDateString()} - ${new Date(options.dateRange.end * 1000).toLocaleDateString()}` - : '全部' - - let html = ` - - - - - ${this.escapeHtml(title)} - - - -
-
-

${this.escapeHtml(sessionInfo.displayName)}

-
-
共 ${totalMessages} 条消息
-
时间范围: ${dateRange}
-
导出时间: ${new Date().toLocaleString('zh-CN')}
-
-
-
- - - - - - - 1 / 1 - - -
-
-
加载中...
-
- -
- - - -` - - return html - } - - /** - * 格式化消息内容为 HTML - */ - private formatMessageContent(msg: any): string { - const typeTag = this.getMessageTypeText(msg.type) - - if (msg.type === 1) { - // 文本消息 - return this.escapeHtml(msg.content || '') - } else if (msg.type === 3) { - return `📷 ${typeTag}` - } else if (msg.type === 34) { - return `🎤 ${typeTag}` - } else if (msg.type === 43) { - return `🎬 ${typeTag}` - } else if (msg.type === 47) { - return `😊 ${typeTag}` - } else if (msg.type === 48) { - return `📍 ${typeTag}` - } else if (msg.type === 49) { - return `🔗 ${typeTag}` - } else if (msg.type === 42) { - return `👤 ${typeTag}` - } else if (msg.type === 50) { - return `📞 ${typeTag}` - } else { - return `${typeTag}` - } - } - - /** - * HTML 转义 - */ - private escapeHtml(text: string): string { - const map: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } - return text.replace(/[&<>"']/g, m => map[m]) - } - - /** - * 获取消息类型文本 - */ - private getMessageTypeText(type: number): string { - const typeMap: Record = { + private getChatRecordTypeName(datatype: number): string { + const typeNames: Record = { 1: '文本', 3: '图片', + 8: '文件', 34: '语音', 43: '视频', 47: '表情', - 48: '位置', - 49: '链接/文件', - 42: '名片', - 50: '通话', - 10000: '系统消息' + 49: '文件' } - return typeMap[type] || `未知(${type})` + return typeNames[datatype] || '其他' } /** - * 获取消息内容(简化版) + * 获取聊天记录消息的内容 */ - private getMessageContent(msg: any): string { - if (!msg.content) return '' - - // 文本消息 - if (msg.type === 1) { - return msg.content + private getChatRecordContent(record: any): string { + switch (record.datatype) { + case 1: + return record.datadesc || record.datatitle || '' + case 3: + return '[图片]' + case 8: + case 49: + return record.datatitle ? `[文件] ${record.datatitle}` : '[文件]' + case 34: + return '[语音消息]' + case 43: + return '[视频]' + case 47: + return '[动画表情]' + default: + return record.datadesc || record.datatitle || '[消息]' } - - // 图片 - if (msg.type === 3) { - return '[图片]' - } - - // 语音 - if (msg.type === 34) { - return '[语音]' - } - - // 视频 - if (msg.type === 43) { - return '[视频]' - } - - // 表情 - if (msg.type === 47) { - return '[表情]' - } - - // 位置 - if (msg.type === 48) { - return '[位置]' - } - - // 链接/文件 - if (msg.type === 49) { - try { - if (msg.content.includes('5')) return '[链接]' - if (msg.content.includes('6')) return '[文件]' - if (msg.content.includes('57')) return '[引用消息]' - } catch {} - return '[链接/文件]' - } - - // 名片 - if (msg.type === 42) { - return '[名片]' - } - - // 通话 - if (msg.type === 50) { - return '[通话]' - } - - // 系统消息 - if (msg.type === 10000) { - return msg.content - } - - return msg.content.substring(0, 100) } /** @@ -1900,8 +1905,8 @@ class ExportService { total: sessionIds.length, currentSession: sessionInfo.displayName, phase: 'exporting', - detail: '正在读取消息...' - }) + detail: '正在读取消息...' + }) // 生成文件名(清理非法字符) const safeName = sessionInfo.displayName.replace(/[<>:"/\\|?*]/g, '_') @@ -1951,12 +1956,16 @@ class ExportService { } } + /** + * 导出通讯录 + */ /** * 导出通讯录 */ async exportContacts( outputDir: string, - options: ContactExportOptions + options: ContactExportOptions, + onProgress?: (progress: ExportProgress) => void ): Promise<{ success: boolean; successCount?: number; error?: string }> { try { if (!this.dbDir) { @@ -1966,6 +1975,14 @@ class ExportService { } } + onProgress?.({ + current: 0, + total: 100, + currentSession: '通讯录', + phase: 'preparing', + detail: '正在连接数据库...' + }) + if (!this.contactDb) { return { success: false, error: '联系人数据库未连接' } } @@ -2004,6 +2021,14 @@ class ExportService { if (hasExtraBuffer) selectCols.push('extra_buffer') if (hasDescription) selectCols.push('description') + onProgress?.({ + current: 20, + total: 100, + currentSession: '通讯录', + phase: 'exporting', + detail: '正在读取联系人数据...' + }) + const rows = this.contactDb.prepare(` SELECT ${selectCols.join(', ')} FROM contact `).all() as any[] @@ -2039,13 +2064,6 @@ class ExportService { } // 仅当没有指定选中列表时,才应用类型过滤 - // 如果指定了选中列表,则忽略类型过滤(用户选了啥就导啥) - // 或者也可以保留类型过滤?通常全选导出时类型过滤有用,手动选择时类型过滤可能造成困扰。 - // 根据需求 "不选中或者全选就默认导出全部",这里的 "全部" 应该是指符合 contactTypes 筛选条件的全部。 - // 而手动选择时,应该是明确指定要导出的。 - // 所以逻辑是:如果有 selectedUsernames,则直接导出选中的(不看 type 过滤); - // 如果没有 selectedUsernames(空),则应用 type 过滤导出全部符合类型的。 - if (!options.selectedUsernames || options.selectedUsernames.length === 0) { if (type === 'friend' && !options.contactTypes.friends) continue if (type === 'group' && !options.contactTypes.groups) continue @@ -2083,6 +2101,14 @@ class ExportService { }) } + onProgress?.({ + current: 60, + total: 100, + currentSession: '通讯录', + phase: 'writing', + detail: `正在处理 ${contacts.length} 个联系人...` + }) + // 按类型和名称排序 contacts.sort((a, b) => { const typeOrder: Record = { friend: 0, group: 1, official: 2, other: 3 } @@ -2164,6 +2190,14 @@ class ExportService { return { success: false, error: `不支持的格式: ${options.format}` } } + onProgress?.({ + current: 100, + total: 100, + currentSession: '通讯录', + phase: 'complete', + detail: '导出完成' + }) + return { success: true, successCount: contacts.length } } catch (e) { console.error('ExportService: 导出通讯录失败:', e) diff --git a/electron/services/htmlExportGenerator.ts b/electron/services/htmlExportGenerator.ts new file mode 100644 index 0000000..124a392 --- /dev/null +++ b/electron/services/htmlExportGenerator.ts @@ -0,0 +1,865 @@ +/** + * HTML 导出生成器 + * 负责生成聊天记录的 HTML 展示页面 + * 使用外部资源引用,避免文件过大 + */ + +export interface HtmlExportMessage { + timestamp: number + sender: string + senderName: string + type: number + content: string | null + rawContent: string + isSend: boolean + chatRecords?: HtmlChatRecord[] +} + +export interface HtmlChatRecord { + sender: string + senderDisplayName: string + timestamp: number + formattedTime: string + type: string + datatype: number + content: string + senderAvatar?: string + fileExt?: string + fileSize?: number +} + +export interface HtmlMember { + id: string + name: string + avatar?: string +} + +export interface HtmlExportData { + meta: { + sessionId: string + sessionName: string + isGroup: boolean + exportTime: number + messageCount: number + dateRange: { start: number; end: number } | null + } + members: HtmlMember[] + messages: HtmlExportMessage[] +} + +export class HtmlExportGenerator { + /** + * 生成 HTML 主文件(引用外部 CSS 和 JS) + */ + static generateHtmlWithData(exportData: HtmlExportData): string { + const escapedSessionName = this.escapeHtml(exportData.meta.sessionName) + const dateRangeText = exportData.meta.dateRange + ? `${new Date(exportData.meta.dateRange.start * 1000).toLocaleDateString('zh-CN')} - ${new Date(exportData.meta.dateRange.end * 1000).toLocaleDateString('zh-CN')}` + : '' + + return ` + + + + + ${escapedSessionName} - 聊天记录 + + + + +
+
+

${escapedSessionName}

+
+ 共 ${exportData.messages.length} 条消息 + ${dateRangeText ? ` | ${dateRangeText}` : ''} +
+
+ +
+ + + +
+ 共 ${exportData.messages.length} 条消息 + +
+
+ +
+
+
正在加载聊天记录...
+
+
+ + +
+ + + + +`; + } + + /** + * 生成外部 CSS 文件 + */ + static generateCss(): string { + return `/* CipherTalk 聊天记录导出样式 */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding: 20px; + line-height: 1.6; + color: #333; +} + +.container { + max-width: 1000px; + margin: 0 auto; + background: white; + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + overflow: hidden; + animation: slideIn 0.5s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 头部样式 */ +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 40px 30px; + text-align: center; + position: relative; + overflow: hidden; +} + +.header::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); + animation: pulse 15s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } +} + +.header h1 { + font-size: 32px; + margin-bottom: 12px; + font-weight: 700; + position: relative; + z-index: 1; + text-shadow: 0 2px 10px rgba(0,0,0,0.2); +} + +.header .meta { + font-size: 15px; + opacity: 0.95; + position: relative; + z-index: 1; +} + +/* 控制栏样式 */ +.controls { + position: sticky; + top: 0; + background: white; + padding: 20px; + border-bottom: 2px solid #f0f0f0; + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; + z-index: 100; + box-shadow: 0 2px 8px rgba(0,0,0,0.05); +} + +.controls input[type="text"] { + flex: 1; + min-width: 250px; + padding: 12px 16px; + border: 2px solid #e0e0e0; + border-radius: 8px; + font-size: 14px; + transition: all 0.3s; +} + +.controls input[type="text"]:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.controls button { + padding: 12px 24px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + transition: all 0.3s; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.controls button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4); +} + +.controls button:active { + transform: translateY(0); +} + +.controls .stats { + display: flex; + gap: 12px; + align-items: center; + margin-left: auto; + font-size: 14px; + color: #666; +} + +.controls .stats span { + font-weight: 500; +} + +/* 滚动容器 */ +.scroll-container { + height: calc(100vh - 280px); + overflow-y: auto; + overflow-x: hidden; + position: relative; + will-change: scroll-position; + -webkit-overflow-scrolling: touch; +} + +.scroll-container::-webkit-scrollbar { + width: 8px; +} + +.scroll-container::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.scroll-container::-webkit-scrollbar-thumb { + background: #888; + border-radius: 4px; +} + +.scroll-container::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* 消息容器 */ +.messages { + padding: 20px; + background: #fafafa; +} + +.message-placeholder { + height: 80px; + display: flex; + align-items: center; + justify-content: center; + color: #999; + font-size: 14px; +} + +.loading, +.error, +.no-messages { + text-align: center; + padding: 60px 20px; + font-size: 16px; +} + +.loading { + color: #999; +} + +.error { + color: #d32f2f; +} + +.no-messages { + color: #999; +} + +/* 消息样式 */ +.message { + display: flex; + margin-bottom: 20px; + opacity: 1; + transition: opacity 0.2s; +} + +.message:last-child { + margin-bottom: 0; +} + +.message.sent { + flex-direction: row-reverse; +} + +.message .avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + flex-shrink: 0; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 700; + font-size: 16px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.message .avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.message .content-wrapper { + max-width: 65%; + margin: 0 10px; +} + +.message.sent .content-wrapper { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.message .sender-name { + font-size: 12px; + color: #666; + margin-bottom: 4px; + font-weight: 500; + line-height: 1.2; +} + +.message .bubble { + background: white; + padding: 10px 14px; + border-radius: 12px; + word-wrap: break-word; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; + position: relative; + box-shadow: 0 1px 4px rgba(0,0,0,0.08); + transition: box-shadow 0.2s; + max-width: 100%; + line-height: 1.5; +} + +.message .bubble:hover { + box-shadow: 0 2px 8px rgba(0,0,0,0.12); +} + +.message.sent .bubble { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25); +} + +.message .time { + font-size: 11px; + color: #999; + margin-top: 4px; + line-height: 1.2; +} + +.message.sent .time { + text-align: right; +} + +/* 聊天记录引用 */ +.chat-records { + margin-top: 8px; + padding: 8px 10px; + background: rgba(0,0,0,0.04); + border-radius: 8px; + border-left: 3px solid #667eea; +} + +.message.sent .chat-records { + background: rgba(255,255,255,0.15); + border-left-color: rgba(255,255,255,0.6); +} + +.chat-records .title { + font-size: 12px; + font-weight: 700; + margin-bottom: 6px; + color: #667eea; + line-height: 1.2; +} + +.message.sent .chat-records .title { + color: rgba(255,255,255,0.95); +} + +.chat-record-item { + font-size: 12px; + padding: 6px 0; + border-bottom: 1px solid rgba(0,0,0,0.06); + line-height: 1.4; +} + +.chat-record-item:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.chat-record-item .record-sender { + font-weight: 600; + color: #333; +} + +.message.sent .chat-record-item .record-sender { + color: rgba(255,255,255,0.95); +} + +.chat-record-item .record-time { + font-size: 10px; + color: #999; + margin-left: 8px; +} + +.message.sent .chat-record-item .record-time { + color: rgba(255,255,255,0.75); +} + +.chat-record-item .record-content { + margin-top: 2px; + color: #666; + line-height: 1.4; + word-wrap: break-word; + word-break: break-word; + white-space: pre-wrap; + overflow-wrap: break-word; +} + +.message.sent .chat-record-item .record-content { + color: rgba(255,255,255,0.9); +} + +/* 页脚 */ +.footer { + text-align: center; + padding: 24px; + color: #999; + font-size: 13px; + border-top: 2px solid #f0f0f0; + background: #fafafa; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + .container { + border-radius: 12px; + } + + .header { + padding: 30px 20px; + } + + .header h1 { + font-size: 24px; + } + + .controls { + padding: 15px; + } + + .controls input[type="text"] { + min-width: 100%; + } + + .controls .stats { + width: 100%; + justify-content: center; + margin-left: 0; + margin-top: 10px; + } + + .scroll-container { + height: calc(100vh - 320px); + } + + .messages { + padding: 20px 15px; + } + + .message .content-wrapper { + max-width: 75%; + } +} + +/* 打印样式 */ +@media print { + body { + background: white; + padding: 0; + } + + .container { + box-shadow: none; + border-radius: 0; + } + + .controls { + display: none; + } + + .message { + page-break-inside: avoid; + } +}`; + } + + /** + * 生成数据 JS 文件(作为全局变量) + */ + static generateDataJs(exportData: HtmlExportData): string { + return `// CipherTalk 聊天记录数据 +window.CHAT_DATA = ${JSON.stringify(exportData, null, 2)};`; + } + + /** + * 生成外部 JavaScript 文件 + */ + static generateJs(): string { + return `// CipherTalk 聊天记录导出应用 + +class ChatApp { + constructor() { + this.allData = window.CHAT_DATA; + this.filteredMessages = this.allData.messages; + + // 无感加载配置 + this.batchSize = 30; // 每次加载30条 + this.loadedCount = 0; // 已加载数量 + this.isLoading = false; // 是否正在加载 + + // DOM 元素 + this.scrollContainer = null; + this.messagesContainer = null; + this.loadMoreObserver = null; + this.sentinel = null; // 哨兵元素 + + this.init(); + } + + init() { + try { + if (!this.allData) { + throw new Error('数据加载失败'); + } + + // 获取DOM元素 + this.scrollContainer = document.getElementById('scrollContainer'); + this.messagesContainer = document.getElementById('messagesContainer'); + + // 清空容器 + this.messagesContainer.innerHTML = ''; + + // 绑定事件 + this.bindEvents(); + + // 设置 Intersection Observer(必须在 loadMoreMessages 之前) + this.setupIntersectionObserver(); + + // 初始加载 + this.loadMoreMessages(); + + // 更新统计信息 + this.updateStats(); + } catch (error) { + console.error('初始化失败:', error); + document.getElementById('messagesContainer').innerHTML = + \`
加载失败: \${error.message}
\`; + } + } + + bindEvents() { + // 搜索框回车 + const searchInput = document.getElementById('searchInput'); + searchInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.searchMessages(); + } + }); + } + + setupIntersectionObserver() { + // 创建哨兵元素 + this.sentinel = document.createElement('div'); + this.sentinel.className = 'message-placeholder'; + this.sentinel.textContent = '加载中...'; + this.sentinel.style.display = 'none'; + + // 创建 Intersection Observer + this.loadMoreObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !this.isLoading) { + this.loadMoreMessages(); + } + }); + }, { + root: this.scrollContainer, + rootMargin: '200px', // 提前200px开始加载 + threshold: 0.1 + }); + } + + loadMoreMessages() { + if (this.isLoading) return; + if (this.loadedCount >= this.filteredMessages.length) { + // 所有消息已加载完毕 + if (this.sentinel && this.sentinel.parentNode) { + this.sentinel.remove(); + } + return; + } + + this.isLoading = true; + + // 计算本次加载的范围 + const start = this.loadedCount; + const end = Math.min(start + this.batchSize, this.filteredMessages.length); + const batch = this.filteredMessages.slice(start, end); + + // 创建文档片段 + const fragment = document.createDocumentFragment(); + + // 渲染消息 + batch.forEach(msg => { + const messageElement = this.createMessageElement(msg); + fragment.appendChild(messageElement); + }); + + // 移除旧的哨兵 + if (this.sentinel && this.sentinel.parentNode) { + this.sentinel.remove(); + } + + // 添加消息到容器 + this.messagesContainer.appendChild(fragment); + + // 更新已加载数量 + this.loadedCount = end; + + // 如果还有更多消息,添加哨兵 + if (this.loadedCount < this.filteredMessages.length) { + this.sentinel.style.display = 'flex'; + this.messagesContainer.appendChild(this.sentinel); + + // 观察哨兵 + this.loadMoreObserver.observe(this.sentinel); + } + + this.isLoading = false; + this.updateStats(); + } + + createMessageElement(msg) { + const div = document.createElement('div'); + div.className = msg.isSend ? 'message sent' : 'message'; + div.innerHTML = this.renderMessage(msg); + return div; + } + + renderMessage(msg) { + const member = this.allData.members.find(m => m.id === msg.sender); + const senderName = member ? member.name : msg.senderName; + const avatar = member && member.avatar ? member.avatar : null; + const time = new Date(msg.timestamp * 1000).toLocaleString('zh-CN'); + + // 生成头像 + let avatarHtml = ''; + if (avatar) { + avatarHtml = \`\${this.escapeHtml(senderName)}\`; + } else { + avatarHtml = senderName.charAt(0).toUpperCase(); + } + + // 生成消息内容 + let contentHtml = msg.content ? this.escapeHtml(msg.content) : '无内容'; + + // 如果有聊天记录,添加聊天记录展示 + let chatRecordsHtml = ''; + if (msg.chatRecords && msg.chatRecords.length > 0) { + chatRecordsHtml = '
'; + chatRecordsHtml += '
📋 聊天记录引用
'; + for (const record of msg.chatRecords) { + chatRecordsHtml += \` +
+
+ \${this.escapeHtml(record.senderDisplayName)} + \${this.escapeHtml(record.formattedTime)} +
+
\${this.escapeHtml(record.content)}
+
+ \`; + } + chatRecordsHtml += '
'; + } + + return \` +
\${avatarHtml}
+
+
\${this.escapeHtml(senderName)}
+
+ \${contentHtml} + \${chatRecordsHtml} +
+
\${time}
+
+ \`; + } + + searchMessages() { + const keyword = document.getElementById('searchInput').value.trim().toLowerCase(); + if (!keyword) { + this.filteredMessages = this.allData.messages; + } else { + this.filteredMessages = this.allData.messages.filter(msg => { + // 搜索消息内容 + if (msg.content && msg.content.toLowerCase().includes(keyword)) { + return true; + } + // 搜索发送者名称 + const member = this.allData.members.find(m => m.id === msg.sender); + const senderName = member ? member.name : msg.senderName; + if (senderName.toLowerCase().includes(keyword)) { + return true; + } + // 搜索聊天记录内容 + if (msg.chatRecords) { + for (const record of msg.chatRecords) { + if (record.content.toLowerCase().includes(keyword) || + record.senderDisplayName.toLowerCase().includes(keyword)) { + return true; + } + } + } + return false; + }); + } + + // 重置并重新加载 + this.reset(); + } + + clearSearch() { + document.getElementById('searchInput').value = ''; + this.filteredMessages = this.allData.messages; + this.reset(); + } + + reset() { + // 停止观察 + if (this.loadMoreObserver && this.sentinel && this.sentinel.parentNode) { + this.loadMoreObserver.unobserve(this.sentinel); + } + + // 清空容器 + this.messagesContainer.innerHTML = ''; + + // 重置状态 + this.loadedCount = 0; + this.isLoading = false; + + // 滚动到顶部 + this.scrollContainer.scrollTop = 0; + + // 重新设置观察器(必须在 loadMoreMessages 之前) + this.setupIntersectionObserver(); + + // 重新加载 + this.loadMoreMessages(); + } + + updateStats() { + const totalCount = this.filteredMessages.length; + document.getElementById('messageStats').textContent = \`共 \${totalCount} 条消息\`; + document.getElementById('loadedStats').textContent = \`已加载 \${this.loadedCount} 条\`; + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } +} + +// 初始化应用 +const app = new ChatApp();`; + } + + /** + * 生成数据 JSON 文件 + */ + static generateDataJson(exportData: HtmlExportData): string { + return JSON.stringify(exportData, null, 2); + } + + /** + * HTML 转义 + */ + private static escapeHtml(text: string): string { + const map: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + return text.replace(/[&<>"']/g, m => map[m]) + } +} \ No newline at end of file diff --git a/electron/services/imageDecryptService.ts b/electron/services/imageDecryptService.ts index 5be7e5a..b5ca9eb 100644 --- a/electron/services/imageDecryptService.ts +++ b/electron/services/imageDecryptService.ts @@ -158,7 +158,7 @@ export class ImageDecryptService { return { success: false, error: '未找到高清图,请在微信中点开该图片查看后重试' } } if (!datPath) { - console.error(`[ImageDecrypt] 未找到图片文件: ${payload.imageDatName || payload.imageMd5} sessionId=${payload.sessionId}`) + console.warn(`[ImageDecrypt] 未找到图片文件: ${payload.imageDatName || payload.imageMd5} sessionId=${payload.sessionId}`) return { success: false, error: '未找到图片文件' } } @@ -622,8 +622,9 @@ export class ImageDecryptService { continue } - // 构建路径: msg/attach/{dir1Name}/{dir2Name}/Img/{fileName} + // 构建可能的所有路径结构(仅限 msg/attach) const possiblePaths = [ + // 常见结构: msg/attach/xx/yy/Img/name join(accountDir, 'msg', 'attach', dir1Name, dir2Name, 'Img', fileName), join(accountDir, 'msg', 'attach', dir1Name, dir2Name, 'mg', fileName), join(accountDir, 'msg', 'attach', dir1Name, dir2Name, fileName), @@ -719,7 +720,7 @@ export class ImageDecryptService { const lowerName = entry.name.toLowerCase() // 顶层目录过滤 if (depth === 0) { - if (['fileStorage', 'image', 'image2', 'msg', 'attach', 'img'].some(k => lowerName.includes(k))) { + if (['image', 'image2', 'msg', 'attach', 'img'].some(k => lowerName.includes(k))) { queue.push({ path: fullPath, depth: depth + 1 }) } } else { @@ -767,7 +768,12 @@ export class ImageDecryptService { } private isThumbnailDat(fileName: string): boolean { - return fileName.includes('.t.dat') || fileName.includes('_t.dat') + const lower = fileName.toLowerCase() + return ( + lower.includes('.t.dat') || + lower.includes('_t.dat') || + lower.includes('_thumb.dat') + ) } private hasXVariant(baseLower: string): boolean { @@ -780,7 +786,11 @@ export class ImageDecryptService { const ext = extname(lower) const base = ext ? lower.slice(0, -ext.length) : lower // 支持新命名 _thumb 和旧命名 _t - return base.endsWith('_t') || base.endsWith('_thumb') + return ( + base.endsWith('_t') || + base.endsWith('_thumb') || + base.endsWith('.t') + ) } private isHdPath(filePath: string): boolean { @@ -1187,7 +1197,7 @@ export class ImageDecryptService { roots.push(oldPath) // 去重 - const uniqueRoots = [...new Set(roots)] + const uniqueRoots = Array.from(new Set(roots)) // 过滤存在的路径 const existingRoots = uniqueRoots.filter(r => existsSync(r)) @@ -1773,13 +1783,13 @@ export class ImageDecryptService { * 清理 hardlink 数据库缓存(用于增量更新时释放文件) */ clearHardlinkCache(): void { - for (const [accountDir, state] of this.hardlinkCache.entries()) { + this.hardlinkCache.forEach((state, accountDir) => { try { state.db.close() } catch (e) { console.warn(`关闭 hardlink 数据库失败: ${accountDir}`, e) } - } + }) this.hardlinkCache.clear() } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e7bdcd6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8502 @@ +{ + "name": "ciphertalk", + "version": "2.1.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ciphertalk", + "version": "2.1.6", + "hasInstallScript": true, + "license": "CC-BY-NC-SA-4.0", + "dependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@types/react-virtualized-auto-sizer": "^1.0.4", + "@types/react-window": "^1.8.8", + "@xmldom/xmldom": "^0.9.6", + "better-sqlite3": "^12.5.0", + "dom-to-image-more": "^3.7.2", + "dompurify": "^3.3.1", + "echarts": "^5.5.1", + "echarts-for-react": "^3.0.2", + "electron-store": "^10.0.0", + "electron-updater": "^6.3.9", + "ffmpeg-static": "^5.3.0", + "fzstd": "^0.1.1", + "html2canvas": "^1.4.1", + "https-proxy-agent": "^7.0.6", + "jieba-wasm": "^2.2.0", + "jszip": "^3.10.1", + "koffi": "^2.9.0", + "lucide-react": "^0.562.0", + "marked": "^17.0.1", + "openai": "^4.70.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router-dom": "^7.1.1", + "react-virtualized-auto-sizer": "^2.0.2", + "react-window": "^2.2.5", + "sherpa-onnx-node": "^1.12.23", + "silk-wasm": "^3.7.1", + "wechat-emojis": "^1.0.2", + "xlsx": "^0.18.5", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@electron/rebuild": "^4.0.2", + "@types/better-sqlite3": "^7.6.13", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.3.4", + "electron": "^39.2.7", + "electron-builder": "^25.1.8", + "sass": "^1.83.0", + "sharp": "^0.34.5", + "typescript": "^5.6.3", + "vite": "^6.0.5", + "vite-plugin-electron": "^0.28.8", + "vite-plugin-electron-renderer": "^0.14.6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "license": "MIT", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "got": "^11.7.0", + "graceful-fs": "^4.2.11", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^11.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.7", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.4", + "@parcel/watcher-darwin-arm64": "2.5.4", + "@parcel/watcher-darwin-x64": "2.5.4", + "@parcel/watcher-freebsd-x64": "2.5.4", + "@parcel/watcher-linux-arm-glibc": "2.5.4", + "@parcel/watcher-linux-arm-musl": "2.5.4", + "@parcel/watcher-linux-arm64-glibc": "2.5.4", + "@parcel/watcher-linux-arm64-musl": "2.5.4", + "@parcel/watcher-linux-x64-glibc": "2.5.4", + "@parcel/watcher-linux-x64-musl": "2.5.4", + "@parcel/watcher-win32-arm64": "2.5.4", + "@parcel/watcher-win32-ia32": "2.5.4", + "@parcel/watcher-win32-x64": "2.5.4" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.4", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz", + "integrity": "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz", + "integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz", + "integrity": "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz", + "integrity": "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz", + "integrity": "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz", + "integrity": "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz", + "integrity": "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz", + "integrity": "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz", + "integrity": "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz", + "integrity": "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz", + "integrity": "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz", + "integrity": "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.9", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/react": { + "version": "19.2.8", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-virtualized-auto-sizer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz", + "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmmirror.com/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.8", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.9.8.tgz", + "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.10", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.1", + "@electron/rebuild": "3.6.1", + "@electron/universal": "2.0.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chromium-pickle-js": "^0.2.0", + "config-file-ts": "0.2.8-rc1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "25.1.7", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.0", + "resedit": "^1.7.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "25.1.8", + "electron-builder-squirrel-windows": "25.1.8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/rebuild": { + "version": "3.6.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/app-builder-lib/node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/app-builder-lib/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/app-builder-lib/node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-lib/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/app-builder-lib/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-lib/node_modules/minipass-fetch": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/app-builder-lib/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-lib/node_modules/negotiator": { + "version": "0.6.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/app-builder-lib/node_modules/node-abi": { + "version": "3.86.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/node-gyp": { + "version": "9.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/app-builder-lib/node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/app-builder-lib/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/app-builder-lib/node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/aproba": { + "version": "2.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.15", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/better-sqlite3": { + "version": "12.6.2", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "25.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.10", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001765", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/caseless": { + "version": "0.12.0", + "license": "Apache-2.0" + }, + "node_modules/cfb": { + "version": "1.2.2", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/conf": { + "version": "14.0.0", + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "atomically": "^2.0.3", + "debounce-fn": "^6.0.0", + "dot-prop": "^9.0.0", + "env-paths": "^3.0.0", + "json-schema-typed": "^8.0.1", + "semver": "^7.7.2", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv": { + "version": "8.17.1", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/conf/node_modules/env-paths": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.8-rc1", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.12", + "typescript": "^5.4.3" + } + }, + "node_modules/config-file-ts/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/debounce-fn": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dom-to-image-more": { + "version": "3.7.2", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dot-prop": { + "version": "9.0.0", + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/echarts": { + "version": "5.6.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.5", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "39.2.7", + "resolved": "https://registry.npmmirror.com/electron/-/electron-39.2.7.tgz", + "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "25.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-store": { + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "conf": "^14.0.0", + "type-fest": "^4.41.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-updater": { + "version": "6.7.3", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.5.1", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "~7.7.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.5.1", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.2.0", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "22.19.7", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/ffmpeg-static": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz", + "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==", + "hasInstallScript": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ffmpeg-static/node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ffmpeg-static/node_modules/https-proxy-agent": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fzstd": { + "version": "0.1.1", + "license": "MIT" + }, + "node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-response-object": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "license": "MIT" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jieba-wasm": { + "version": "2.4.0", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "license": "BSD-2-Clause" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/koffi": { + "version": "2.14.1", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://buymeacoffee.com/koromix" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.562.0", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmmirror.com/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.1.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "4.25.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "11.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "7.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "4.70.0", + "resolved": "https://registry.npmmirror.com/openai/-/openai-4.70.0.tgz", + "integrity": "sha512-Hz8lRAH2oXBG9ct0mssSag/iJz9vrH+k6hSl3enwwtnJz5x9sCaBmWNcYYTsPmkz3GmgUUD102GRrWWBbufqFQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/ora": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-cache-control": { + "version": "1.0.1" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/pe-library": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/node-abi": { + "version": "3.86.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.3", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.12.0", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.12.0", + "license": "MIT", + "dependencies": { + "react-router": "7.12.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-virtualized-auto-sizer": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-2.0.2.tgz", + "integrity": "sha512-FvnVDed3nn7Xt2m2ioo+O1VBpP1uMIl8ygtpkzfhYoRb1e06on6hp2DEBg9AquCXqtP1bhgVT4lS+xpBwrXq7Q==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-window": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/react-window/-/react-window-2.2.5.tgz", + "integrity": "sha512-6viWvPSZvVuMIe9hrl4IIZoVfO/npiqOb03m4Z9w+VihmVzBbiudUrtUqDpsWdKvd/Ai31TCR25CBcFFAUm28w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "devOptional": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sass": { + "version": "1.97.2", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.34.5", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/sharp/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/sharp/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sherpa-onnx-node": { + "version": "1.12.23", + "license": "Apache-2.0", + "optionalDependencies": { + "sherpa-onnx-darwin-arm64": "^1.12.23", + "sherpa-onnx-darwin-x64": "^1.12.23", + "sherpa-onnx-linux-arm64": "^1.12.23", + "sherpa-onnx-linux-x64": "^1.12.23", + "sherpa-onnx-win-ia32": "^1.12.23", + "sherpa-onnx-win-x64": "^1.12.23" + } + }, + "node_modules/sherpa-onnx-node/node_modules/sherpa-onnx-darwin-arm64": { + "version": "1.12.23", + "resolved": "https://registry.npmmirror.com/sherpa-onnx-darwin-arm64/-/sherpa-onnx-darwin-arm64-1.12.23.tgz", + "integrity": "sha512-zbjNUUH/IXhjRyRJ9mpcWVOGIVr31a/qXBPsfOYc7U8cgwcq33Vmj2OzoLYWQF6T+puqCAE4nMxFAxJvdZekhg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sherpa-onnx-node/node_modules/sherpa-onnx-darwin-x64": { + "optional": true + }, + "node_modules/sherpa-onnx-node/node_modules/sherpa-onnx-linux-arm64": { + "optional": true + }, + "node_modules/sherpa-onnx-node/node_modules/sherpa-onnx-linux-x64": { + "version": "1.12.23", + "resolved": "https://registry.npmmirror.com/sherpa-onnx-linux-x64/-/sherpa-onnx-linux-x64-1.12.23.tgz", + "integrity": "sha512-pUZIdDvPtyRXQDGo9R9MIBf2AFUzfgcGmutoulsEdH3hpK6JteR7Z/5pfrZIIqe/O99djAjEHK4AlwLHC2jiZw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sherpa-onnx-node/node_modules/sherpa-onnx-win-ia32": { + "version": "1.12.23", + "resolved": "https://registry.npmmirror.com/sherpa-onnx-win-ia32/-/sherpa-onnx-win-ia32-1.12.23.tgz", + "integrity": "sha512-MyLsK7r6dd7paglyTgb8UHTXTEFqOzA91u6VDV64Lq8rDGuOFVYioxX7vlwmGe1A9o7VhuOPNaKcRjEPtVDhBQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/sherpa-onnx-win-x64": { + "version": "1.12.23", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/silk-wasm": { + "version": "3.7.1", + "license": "MIT", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/size-sensor": { + "version": "1.0.3", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssf": { + "version": "0.11.2", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/temp-file": { + "version": "3.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utrie": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron": { + "version": "0.28.8", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite-plugin-electron-renderer": "*" + }, + "peerDependenciesMeta": { + "vite-plugin-electron-renderer": { + "optional": true + } + } + }, + "node_modules/vite-plugin-electron-renderer": { + "version": "0.14.6", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/wechat-emojis": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/xlsx": { + "version": "0.18.5", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zustand": { + "version": "5.0.10", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index 6570487..f834554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ciphertalk", - "version": "2.1.5", + "version": "2.1.6", "description": "密语 - 微信聊天记录查看工具", "author": "ILoveBingLu", "license": "CC-BY-NC-SA-4.0", @@ -22,6 +22,7 @@ "@types/marked": "^5.0.2", "@types/react-virtualized-auto-sizer": "^1.0.4", "@types/react-window": "^1.8.8", + "@xmldom/xmldom": "^0.9.6", "better-sqlite3": "^12.5.0", "dom-to-image-more": "^3.7.2", "dompurify": "^3.3.1", @@ -144,4 +145,4 @@ "resources/**/*" ] } -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index d8063a1..e1c8f3f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import VideoWindow from './pages/VideoWindow' import BrowserWindowPage from './pages/BrowserWindowPage' import SplashPage from './pages/SplashPage' import AISummaryWindow from './pages/AISummaryWindow' +import ChatHistoryPage from './pages/ChatHistoryPage' import { useAppStore } from './stores/appStore' import { useThemeStore } from './stores/themeStore' import { useChatStore } from './stores/chatStore' @@ -263,6 +264,15 @@ function App() { return } + // 独立聊天记录窗口 + if (location.pathname.startsWith('/chat-history/')) { + return ( +
+ +
+ ) + } + // 独立引导窗口 if (isWelcomeWindow) { return @@ -441,6 +451,7 @@ function App() { } /> } /> } /> + } /> diff --git a/src/components/WhatsNewModal.tsx b/src/components/WhatsNewModal.tsx index 8401203..1e4673b 100644 --- a/src/components/WhatsNewModal.tsx +++ b/src/components/WhatsNewModal.tsx @@ -8,27 +8,27 @@ interface WhatsNewModalProps { function WhatsNewModal({ onClose, version }: WhatsNewModalProps) { const updates = [ - { - icon: , - title: 'BUG修复', - desc: '修复消息内容会出现重复的问题。' - }, + // { + // icon: , + // title: '性能优化', + // desc: '修复消息内容会出现重复的问题。' + // }, { icon: , title: '优化', - desc: '优化AI摘要,新增适配大模型。' - }, - { - icon: , - title: 'AI摘要', - desc: '支持AI在单人会话以及群聊会话中进行AI摘要总结。(默认只能选择天数)' - }, - { - icon: , - title: '体验升级', - desc: '修复了一些已知的问题。' + desc: '修复了一些已知问题。' }//, // { + // icon: , + // title: 'AI摘要', + // desc: '支持AI在单人会话以及群聊会话中进行AI摘要总结。(默认只能选择天数)' + // }, + // { + // icon: , + // title: '体验升级', + // desc: '修复了一些已知的问题。' + // }//, + // { // icon: , // title: '语音增强', // desc: '语音转文字支持多模型选择,灵活平衡识别精度与速度,适配更多场景。' diff --git a/src/pages/ChatHistoryPage.scss b/src/pages/ChatHistoryPage.scss new file mode 100644 index 0000000..ce71548 --- /dev/null +++ b/src/pages/ChatHistoryPage.scss @@ -0,0 +1,124 @@ +.chat-history-page { + height: 100vh; + display: flex; + flex-direction: column; + background-color: var(--bg-secondary); + -webkit-app-region: no-drag; + + .history-list { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; + + .status-msg { + text-align: center; + color: var(--text-tertiary); + margin-top: 40px; + + &.error { + color: #ff4d4f; + } + + &.empty { + color: var(--text-tertiary); + } + } + } + + .history-item { + display: flex; + gap: 10px; + align-items: flex-start; + + // 复用聊天窗口的头像视觉风格 + .avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: transparent; // 只显示头像图片本身,不要描边/底色 + border: none; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .avatar-letter { + font-size: 14px; + font-weight: 600; + color: #fff; + } + } + + .content-wrapper { + flex: 1; + min-width: 0; + + .header { + display: flex; + align-items: baseline; + gap: 8px; + margin-bottom: 4px; + font-size: 12px; + + .sender { + color: var(--text-secondary); + font-weight: 500; + } + + .time { + color: var(--text-tertiary); + font-size: 11px; + } + } + + .bubble { + display: inline-block; + max-width: 70%; + background: var(--bg-secondary); + color: var(--text-primary); + border-radius: 18px 18px 18px 4px; + border: 1px solid var(--border-color); + padding: 8px 12px; + font-size: 14px; + line-height: 1.6; + white-space: pre-wrap; + word-break: break-word; + + &.image-bubble { + padding: 0; + background: transparent; + border: none; + box-shadow: none; + + img { + max-width: 300px; + max-height: 300px; + border-radius: 4px; + cursor: pointer; + } + + .media-tip { + font-size: 12px; + color: var(--text-tertiary); + margin-top: 4px; + } + } + + p { + margin: 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/pages/ChatHistoryPage.tsx b/src/pages/ChatHistoryPage.tsx new file mode 100644 index 0000000..7fdfbd8 --- /dev/null +++ b/src/pages/ChatHistoryPage.tsx @@ -0,0 +1,238 @@ +import React, { useEffect, useState } from 'react' +import { useLocation, useParams } from 'react-router-dom' +import { ChatRecordItem } from '../types/models' +import TitleBar from '../components/TitleBar' +import MessageContent from '../components/MessageContent' +import './ChatHistoryPage.scss' + +export default function ChatHistoryPage() { + const { sessionId, messageId } = useParams<{ sessionId: string; messageId: string }>() + const location = useLocation() + const [recordList, setRecordList] = useState([]) + const [loading, setLoading] = useState(true) + const [title, setTitle] = useState('聊天记录') + const [error, setError] = useState('') + + // 简单的 XML 标签内容提取 + const extractXmlValue = (xml: string, tag: string): string => { + const match = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`).exec(xml) + return match ? match[1] : '' + } + + // 简单的 HTML 实体解码(常见几种即可) + const decodeHtmlEntities = (text?: string): string | undefined => { + if (!text) return text + return text + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + } + + // 前端兜底解析合并转发聊天记录 (与后端逻辑类似) + const parseChatHistory = (content: string): ChatRecordItem[] | undefined => { + try { + const type = extractXmlValue(content, 'type') + if (type !== '19') return undefined + + const match = /[\s\S]*?[\s\S]*?<\/recorditem>/.exec(content) + if (!match) return undefined + + const innerXml = match[1] + const items: ChatRecordItem[] = [] + // 注意这里要使用 [\\s\\S] 而不是写错成 [\\s\\\\s] + const itemRegex = /([\s\S]*?)<\/dataitem>/g + let itemMatch: RegExpExecArray | null + + while ((itemMatch = itemRegex.exec(innerXml)) !== null) { + const attrs = itemMatch[1] + const body = itemMatch[2] + + const datatypeMatch = /datatype="(\d+)"/.exec(attrs) + const datatype = datatypeMatch ? parseInt(datatypeMatch[1]) : 0 + + const sourcename = extractXmlValue(body, 'sourcename') + const sourcetime = extractXmlValue(body, 'sourcetime') + const sourceheadurl = extractXmlValue(body, 'sourceheadurl') + const datadesc = extractXmlValue(body, 'datadesc') + const datatitle = extractXmlValue(body, 'datatitle') + const fileext = extractXmlValue(body, 'fileext') + const datasize = parseInt(extractXmlValue(body, 'datasize') || '0') + const messageuuid = extractXmlValue(body, 'messageuuid') + + const dataurl = extractXmlValue(body, 'dataurl') + const datathumburl = extractXmlValue(body, 'datathumburl') || extractXmlValue(body, 'thumburl') + const datacdnurl = extractXmlValue(body, 'datacdnurl') || extractXmlValue(body, 'cdnurl') + const aeskey = extractXmlValue(body, 'aeskey') || extractXmlValue(body, 'qaeskey') + const md5 = extractXmlValue(body, 'md5') || extractXmlValue(body, 'datamd5') + const imgheight = parseInt(extractXmlValue(body, 'imgheight') || '0') + const imgwidth = parseInt(extractXmlValue(body, 'imgwidth') || '0') + const duration = parseInt(extractXmlValue(body, 'duration') || '0') + + items.push({ + datatype, + sourcename, + sourcetime, + sourceheadurl, + datadesc: decodeHtmlEntities(datadesc), + datatitle: decodeHtmlEntities(datatitle), + fileext, + datasize, + messageuuid, + dataurl: decodeHtmlEntities(dataurl), + datathumburl: decodeHtmlEntities(datathumburl), + datacdnurl: decodeHtmlEntities(datacdnurl), + aeskey: decodeHtmlEntities(aeskey), + md5, + imgheight, + imgwidth, + duration + }) + } + + return items.length > 0 ? items : undefined + } catch (e) { + console.error('前端解析聊天记录失败:', e) + return undefined + } + } + + // 统一从路由参数或 pathname 中解析 sessionId / messageId + const getIds = () => { + if (sessionId && messageId) { + return { sid: sessionId, mid: messageId } + } + // 独立窗口场景下没有 Route 包裹,用 pathname 手动解析 + const match = /^\/chat-history\/([^/]+)\/([^/]+)/.exec(location.pathname) + if (match) { + return { sid: match[1], mid: match[2] } + } + return { sid: '', mid: '' } + } + + useEffect(() => { + const loadData = async () => { + const { sid, mid } = getIds() + if (!sid || !mid) { + setError('无效的聊天记录链接') + setLoading(false) + return + } + try { + const result = await window.electronAPI.chat.getMessage(sid, parseInt(mid, 10)) + if (result.success && result.message) { + const msg = result.message + // 优先使用后端解析好的列表 + let records: ChatRecordItem[] | undefined = msg.chatRecordList + + // 如果后端没有解析到,则在前端兜底解析一次 + if ((!records || records.length === 0) && msg.rawContent) { + records = parseChatHistory(msg.rawContent) || [] + } + + if (records && records.length > 0) { + setRecordList(records) + const match = /(.*?)<\/title>/.exec(msg.rawContent || '') + if (match) setTitle(match[1]) + } else { + setError('暂时无法解析这条聊天记录') + } + } else { + setError(result.error || '获取消息失败') + } + } catch (e) { + console.error(e) + setError('加载详情失败') + } finally { + setLoading(false) + } + } + loadData() + }, [sessionId, messageId]) + + return ( + <div className="chat-history-page"> + <TitleBar title={title} /> + <div className="history-list"> + {loading ? ( + <div className="status-msg">加载中...</div> + ) : error ? ( + <div className="status-msg error">{error}</div> + ) : recordList.length === 0 ? ( + <div className="status-msg empty">暂无可显示的聊天记录</div> + ) : ( + recordList.map((item, i) => ( + <HistoryItem key={i} item={item} /> + )) + )} + </div> + </div> + ) +} + +function HistoryItem({ item }: { item: ChatRecordItem }) { + // sourcetime 在合并转发里有两种格式: + // 1) 时间戳(秒) 2) 已格式化的字符串 "2026-01-21 09:56:46" + let time = '' + if (item.sourcetime) { + if (/^\d+$/.test(item.sourcetime)) { + time = new Date(parseInt(item.sourcetime, 10) * 1000).toLocaleString() + } else { + time = item.sourcetime + } + } + + const renderContent = () => { + if (item.datatype === 1) { + return <MessageContent content={item.datadesc || ''} /> + } + if (item.datatype === 3) { + // Image + const src = item.datathumburl || item.datacdnurl + if (src) { + return ( + <div className="media-content"> + <img src={src} alt="图片" referrerPolicy="no-referrer" onError={(e) => { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement?.insertAdjacentHTML('beforeend', '<div class="media-tip">图片无法加载</div>'); + }} /> + </div> + ) + } + return <div className="media-placeholder">[图片]</div> + } + if (item.datatype === 43) { + return <div className="media-placeholder">[视频] {item.datatitle}</div> + } + if (item.datatype === 34) { + return <div className="media-placeholder">[语音] {item.duration ? (item.duration / 1000).toFixed(0) + '"' : ''}</div> + } + // Fallback + return <div className="text-content">{item.datadesc || item.datatitle || '[不支持的消息类型]'}</div> + } + + return ( + <div className="history-item"> + <div className="avatar"> + {item.sourceheadurl ? ( + <img src={item.sourceheadurl} alt="" referrerPolicy="no-referrer" /> + ) : ( + <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#888', fontSize: '12px' }}> + {item.sourcename?.slice(0, 1)} + </div> + )} + </div> + <div className="content-wrapper"> + <div className="header"> + <span className="sender">{item.sourcename || '未知发送者'}</span> + <span className="time">{time}</span> + </div> + <div className={`bubble ${item.datatype === 3 ? 'image-bubble' : ''}`}> + {renderContent()} + </div> + </div> + </div> + ) +} diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss index 323dea5..1131296 100644 --- a/src/pages/ChatPage.scss +++ b/src/pages/ChatPage.scss @@ -338,6 +338,12 @@ animation: spin 1s linear infinite; } } + + // 日期选择器包装器 + .date-picker-wrapper { + position: relative; + -webkit-app-region: no-drag; + } } } @@ -915,6 +921,7 @@ 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } @@ -926,7 +933,7 @@ &.loading { background: var(--bg-tertiary); - + .avatar-skeleton { z-index: 1; } @@ -1298,13 +1305,13 @@ font-weight: 600; color: white; } - + .avatar-skeleton-wrapper { width: 100%; height: 100%; position: relative; background: var(--bg-tertiary); - + .avatar-skeleton { position: absolute; inset: 0; @@ -1630,17 +1637,17 @@ } } -// 适配发送/接收样式 +// 适配发送/接收样式(跟随主题色) .message-bubble.sent .link-message { - background: #fff; - border: 1px solid rgba(0, 0, 0, 0.1); + background: var(--card-bg); + border: 1px solid var(--border-color); .link-title { - color: #333; + color: var(--text-primary); } .link-desc { - color: #666; + color: var(--text-secondary); } } @@ -1707,6 +1714,73 @@ color: var(--text-tertiary); } } + +// 合并转发聊天记录卡片(复用 link-message 结构,仅做内容布局上的补充) +.chat-record-message { + .link-header { + padding-bottom: 4px; + } + + .chat-record-preview { + display: flex; + flex-direction: column; + gap: 4px; + flex: 1; + min-width: 0; + } + + .chat-record-meta-line { + font-size: 11px; + color: var(--text-tertiary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .chat-record-list { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 70px; + overflow: hidden; + } + + .chat-record-item { + font-size: 12px; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .source-name { + color: var(--text-primary); + font-weight: 500; + margin-right: 4px; + } + + .chat-record-more { + font-size: 12px; + color: var(--primary); + } + + .chat-record-desc { + font-size: 12px; + color: var(--text-secondary); + } + + .chat-record-icon { + width: 40px; + height: 40px; + border-radius: 10px; + background: var(--primary-gradient); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + flex-shrink: 0; + } +} } // 发送的文件消息样式 @@ -1740,7 +1814,7 @@ .transfer-icon { flex-shrink: 0; - + svg { width: 32px; height: 32px; @@ -1776,7 +1850,7 @@ font-size: 12px; color: var(--text-tertiary); margin-bottom: 4px; - + .sender-skeleton { display: inline-block; width: 60px; @@ -1797,6 +1871,41 @@ border-radius: 4px; font-size: 13px; + .quoted-message-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 8px; + min-width: 120px; + } + + .quoted-text-container { + flex: 1; + min-width: 0; + } + + .quoted-image-container { + flex-shrink: 0; + width: 40px; + height: 40px; + border-radius: 4px; + overflow: hidden; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + + .quoted-image-thumb { + width: 100%; + height: 100%; + object-fit: cover; + cursor: pointer; + transition: opacity 0.2s; + + &:hover { + opacity: 0.8; + } + } + } + .quoted-sender { color: var(--primary); font-weight: 500; @@ -1809,6 +1918,7 @@ .quoted-text { color: var(--text-secondary); + word-break: break-all; } } @@ -3121,6 +3231,221 @@ video::-webkit-media-controls-fullscreen-button { } } +.date-picker-dropdown { + position: fixed; + width: 320px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 16px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; + z-index: 9999; + animation: dropdownFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1); + user-select: none; + + .calendar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 4px; + margin-bottom: -4px; + + .current-month { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + } + + .calendar-nav-btn { + width: 28px; + height: 28px; + border: none; + background: transparent; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + + &:hover:not(:disabled) { + background: var(--bg-hover); + color: var(--text-primary); + } + + &:disabled { + opacity: 0.3; + cursor: not-allowed; + } + } + } + + .calendar-weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + text-align: center; + margin-bottom: 4px; + + .weekday { + font-size: 12px; + color: var(--text-tertiary); + font-weight: 500; + padding: 4px 0; + } + } + + .calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + row-gap: 4px; + position: relative; + min-height: 200px; // 确保至少有一定高度 + + .calendar-loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(2px); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + border-radius: 8px; + color: var(--primary); + } + + .calendar-day { + width: 36px; + height: 36px; + margin: 0 auto; + border: none; + background: transparent; + border-radius: 50%; + font-size: 13px; + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + position: relative; + + &.empty { + cursor: default; + pointer-events: none; + } + + &:hover:not(:disabled):not(.selected) { + background: var(--bg-hover); + } + + &.today { + color: var(--primary); + font-weight: 600; + + &::after { + content: ''; + position: absolute; + bottom: 4px; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--primary); + } + + &.selected { + color: #fff; + + &::after { + background: #fff; + } + } + } + + &.selected { + background: var(--primary); + color: #fff; + box-shadow: 0 2px 8px var(--primary-light); + } + + &.disabled { + color: var(--text-tertiary); + opacity: 0.5; + cursor: not-allowed; + } + } + } + + .calendar-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-top: 12px; + border-top: 1px solid var(--border-color); + + .date-jump-today { + padding: 8px 12px; + border: 1px solid var(--border-color); + background: transparent; + border-radius: 8px; + font-size: 12px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--text-tertiary); + } + } + + .date-jump-confirm { + flex: 1; + padding: 8px 16px; + border: none; + border-radius: 8px; + background: var(--primary-gradient); + color: #fff; + font-size: 13px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all 0.2s; + box-shadow: 0 2px 8px var(--primary-light); + + &:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px var(--primary-light); + } + + &:active:not(:disabled) { + transform: translateY(0); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .spin { + animation: spin 1s linear infinite; + } + } + } +} + @keyframes modalFadeIn { from { opacity: 0; @@ -3131,4 +3456,77 @@ video::-webkit-media-controls-fullscreen-button { opacity: 1; transform: translateY(0); } -} \ No newline at end of file +} + +@keyframes dropdownFadeIn { + from { + opacity: 0; + transform: translateY(-8px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} +/* 以下这块旧样式原本强行把聊天记录气泡背景设为白色,会打破主题适配。现在只保留排版,不再设置 background。 */ +.chat-record-message { + min-width: 240px; + max-width: 300px; + padding: 12px; +} + +/* If message-bubble handles background, we don't need background here. + Currently message-bubble has padding. bubble-content usually wraps content. +*/ +.chat-page .message-bubble .chat-record-message { + /* Override bubble padding if needed? No, keep it inside. */ + + .chat-record-title { + font-size: 15px; + font-weight: 500; + margin-bottom: 8px; + color: var(--text-primary); + border-bottom: 1px solid var(--border-color); + padding-bottom: 6px; + } + + .chat-record-list { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 13px; + color: var(--text-secondary); + + .chat-record-item { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.4; + + .source-name { + color: var(--text-tertiary); + margin-right: 4px; + } + } + + .chat-record-desc { + white-space: pre-wrap; + line-height: 1.4; + } + + .chat-record-more { + color: var(--text-tertiary); + margin-top: 2px; + } + } + + .chat-record-footer { + margin-top: 8px; + padding-top: 6px; + border-top: 1px solid var(--border-color); + font-size: 11px; + color: var(--text-tertiary); + } +} + diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 292782f..3bfeeee 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -1,6 +1,6 @@ -import { useState, useEffect, useRef, useCallback, memo } from 'react' +import { useState, useEffect, useRef, useCallback, useMemo } from 'react' import { createPortal } from 'react-dom' -import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, Info, Calendar, Database, Hash, Image as ImageIcon, Play, Video, Copy, ZoomIn, CheckSquare, Check, Edit, Link, Sparkles, FileText, FileArchive } from 'lucide-react' +import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, Info, Calendar, Database, Hash, Image as ImageIcon, Play, Video, Copy, ZoomIn, CheckSquare, Check, Edit, Link, Sparkles, FileText, FileArchive, Users } from 'lucide-react' import { useChatStore } from '../stores/chatStore' import { useUpdateStatusStore } from '../stores/updateStatusStore' import ChatBackground from '../components/ChatBackground' @@ -284,6 +284,15 @@ function ChatPage(_props: ChatPageProps) { const [showEnlargeView, setShowEnlargeView] = useState<{ message: Message; content: string } | null>(null) const [copyToast, setCopyToast] = useState(false) const [showMessageInfo, setShowMessageInfo] = useState<Message | null>(null) // 消息信息弹窗 + const [showDatePicker, setShowDatePicker] = useState(false) // 日期选择器弹窗 + const [selectedDate, setSelectedDate] = useState<string>('') // 选中的日期 (YYYY-MM-DD) + const [viewDate, setViewDate] = useState(new Date()) // 日历当前显示的月份 + const [availableDates, setAvailableDates] = useState<Set<string>>(new Set()) // 当前月份有消息的日期 + const [isLoadingDates, setIsLoadingDates] = useState(false) // 加载日期状态 + const [isJumpingToDate, setIsJumpingToDate] = useState(false) // 正在跳转 + const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null) + const datePickerRef = useRef<HTMLDivElement>(null) // 日期选择器容器引用 + const dateButtonRef = useRef<HTMLButtonElement>(null) // 日期按钮引用 // 检查图片密钥配置(XOR 和 AES 都需要配置) useEffect(() => { @@ -600,6 +609,89 @@ function ChatPage(_props: ChatPageProps) { } }, []) + // 日期跳转处理 + const handleJumpToDate = useCallback(async () => { + if (!selectedDate || !currentSessionId || isJumpingToDate) return + + setIsJumpingToDate(true) + setShowDatePicker(false) + + try { + // 将选中的日期转换为 Unix 时间戳(秒) + const targetDate = new Date(selectedDate) + targetDate.setHours(0, 0, 0, 0) + const targetTimestamp = Math.floor(targetDate.getTime() / 1000) + + const result = await window.electronAPI.chat.getMessagesByDate(currentSessionId, targetTimestamp, 50) + + if (result.success && result.messages && result.messages.length > 0) { + // 清空当前消息并加载新消息 + setMessages(result.messages) + setHasMoreMessages(true) // 假设还有更多历史消息 + setCurrentOffset(result.messages.length) + + // 滚动到顶部显示目标日期的消息 + requestAnimationFrame(() => { + if (messageListRef.current) { + messageListRef.current.scrollTop = 0 + } + }) + } else { + // 没有找到消息,可能日期太新 + console.log('未找到该日期或之后的消息') + } + } catch (e) { + console.error('跳转到日期失败:', e) + } finally { + setIsJumpingToDate(false) + } + }, [selectedDate, currentSessionId, isJumpingToDate, setMessages, setHasMoreMessages]) + + // 加载当前月份有消息的日期 + useEffect(() => { + if (!showDatePicker || !currentSessionId) return + + const fetchDates = async () => { + setIsLoadingDates(true) + try { + const year = viewDate.getFullYear() + const month = viewDate.getMonth() + 1 + // 同时加载上个月和下个月的日期,防止切换时闪烁(这里简单处理只加载当月) + const result = await window.electronAPI.chat.getDatesWithMessages(currentSessionId, year, month) + if (result.success && result.dates) { + setAvailableDates(new Set(result.dates)) + } else { + setAvailableDates(new Set()) + } + } catch (e) { + console.error('加载有消息的日期失败:', e) + } finally { + setIsLoadingDates(false) + } + } + + fetchDates() + }, [viewDate, currentSessionId, showDatePicker]) + + // 点击外部关闭日期选择器 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as Node + // 检查是否点击在日期选择器包装器或下拉框内部 + const isClickInsideWrapper = datePickerRef.current?.contains(target) + const isClickInsideDropdown = (target as Element).closest?.('.date-picker-dropdown') + + if (!isClickInsideWrapper && !isClickInsideDropdown) { + setShowDatePicker(false) + } + } + + if (showDatePicker) { + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + } + }, [showDatePicker]) + // 拖动调节侧边栏宽度 const handleResizeStart = useCallback((e: React.MouseEvent) => { e.preventDefault() @@ -963,6 +1055,181 @@ function ChatPage(_props: ChatPageProps) { > <Sparkles size={18} /> </button> + <div className="date-picker-wrapper" ref={datePickerRef}> + <button + ref={dateButtonRef} + className={`icon-btn date-jump-btn ${showDatePicker ? 'active' : ''}`} + onClick={() => { + if (!showDatePicker && dateButtonRef.current) { + const rect = dateButtonRef.current.getBoundingClientRect() + // 下拉框右边缘与按钮右边缘对齐 + const dropdownWidth = 320 // 增加宽度以容纳日历 + let left = rect.right - dropdownWidth + // 确保不会超出屏幕左边 + if (left < 10) left = 10 + setDropdownPosition({ + top: rect.bottom + 8, + left + }) + // 重置视图到当前选中日期或今天 + setViewDate(selectedDate ? new Date(selectedDate) : new Date()) + } + setShowDatePicker(!showDatePicker) + }} + title="跳转到日期" + > + <Calendar size={18} /> + </button> + {showDatePicker && dropdownPosition && createPortal( + <div + className="date-picker-dropdown" + style={{ + top: dropdownPosition.top, + left: dropdownPosition.left, + position: 'fixed', + zIndex: 99999 + }} + ref={(node) => { + // 简单的点击外部检测需要这个 ref,但我们已经在 useEffect 中处理了关闭逻辑 + // 这里主要是为了确保它能被检测到 + if (node) { + // 将这个 node 关联到 ref,以便 handleClickOutside 可以检查 + // 由于 ref 是针对 div 的,我们可以给 dropdown 一个单独的 ref 或者不使用 ref + // 只要 handleClickOutside 逻辑能工作即可 + } + }} + onMouseDown={(e) => e.stopPropagation()} + > + {/* 日历头部:月份切换 */} + <div className="calendar-header"> + <button + className="calendar-nav-btn" + onClick={() => { + const newDate = new Date(viewDate) + newDate.setMonth(newDate.getMonth() - 1) + setViewDate(newDate) + }} + > + <ChevronDown size={16} style={{ transform: 'rotate(90deg)' }} /> + </button> + <span className="current-month"> + {viewDate.getFullYear()}年 {viewDate.getMonth() + 1}月 + </span> + <button + className="calendar-nav-btn nav-next" + onClick={() => { + const newDate = new Date(viewDate) + newDate.setMonth(newDate.getMonth() + 1) + // 不允许查看未来月份(如果本月是未来) + const now = new Date() + if (newDate.getFullYear() > now.getFullYear() || + (newDate.getFullYear() === now.getFullYear() && newDate.getMonth() > now.getMonth())) { + return + } + setViewDate(newDate) + }} + disabled={ + viewDate.getFullYear() === new Date().getFullYear() && + viewDate.getMonth() === new Date().getMonth() + } + > + <ChevronDown size={16} style={{ transform: 'rotate(-90deg)' }} /> + </button> + </div> + + {/* 星期表头 */} + <div className="calendar-weekdays"> + {['日', '一', '二', '三', '四', '五', '六'].map(d => ( + <div key={d} className="weekday">{d}</div> + ))} + </div> + + {/* 日期网格 */} + <div className="calendar-grid"> + {(() => { + const year = viewDate.getFullYear() + const month = viewDate.getMonth() + + // 当月第一天 + const firstDay = new Date(year, month, 1) + // 当月最后一天 + const lastDay = new Date(year, month + 1, 0) + + const daysInMonth = lastDay.getDate() + const startDayOfWeek = firstDay.getDay() // 0-6 + + const days = [] + // 填充上个月的空位 + for (let i = 0; i < startDayOfWeek; i++) { + days.push(<div key={`empty-${i}`} className="calendar-day empty"></div>) + } + + // 填充当月日期 + const today = new Date() + for (let i = 1; i <= daysInMonth; i++) { + const currentDate = new Date(year, month, i) + const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}` + const isSelected = selectedDate === dateStr + const isToday = today.toDateString() === currentDate.toDateString() + const isFuture = currentDate > today + const hasMessage = availableDates.has(dateStr) + + // 禁用条件:是未来日期,或者(不是未来日期且没有消息) + // 但如果在加载中,暂时不禁用非未来的日期,或者显示加载状态 + const isDisabled = isFuture || (!isFuture && !hasMessage) + + days.push( + <button + key={i} + className={`calendar-day ${isSelected ? 'selected' : ''} ${isToday ? 'today' : ''} ${isDisabled ? 'disabled' : ''}`} + onClick={() => { + if (isDisabled) return + setSelectedDate(dateStr) + }} + disabled={isDisabled} + title={isFuture ? '未来时间' : (!hasMessage ? '无消息' : undefined)} + > + {i} + </button> + ) + } + return days + })()} + {isLoadingDates && ( + <div className="calendar-loading-overlay"> + <Loader2 size={24} className="spin" /> + </div> + )} + </div> + + <div className="calendar-footer"> + <button + className="date-jump-today" + onClick={() => { + const now = new Date() + const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` + setSelectedDate(dateStr) + setViewDate(now) + }} + > + 回到今天 + </button> + <button + className="date-jump-confirm" + onClick={handleJumpToDate} + disabled={!selectedDate || isJumpingToDate} + > + {isJumpingToDate ? ( + <><Loader2 size={14} className="spin" /> 跳转中...</> + ) : ( + '跳转' + )} + </button> + </div> + </div>, + document.body + )} + </div> <button className={`icon-btn detail-btn ${showDetailPanel ? 'active' : ''}`} onClick={toggleDetailPanel} @@ -1534,6 +1801,12 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h () => imageDataUrlCache.get(imageCacheKey) ) + // 引用图片缓存 + const quotedImageCacheKey = message.quotedImageMd5 || '' + const [quotedImageLocalPath, setQuotedImageLocalPath] = useState<string | undefined>( + () => quotedImageCacheKey ? imageDataUrlCache.get(quotedImageCacheKey) : undefined + ) + const formatTime = (timestamp: number): string => { const date = new Date(timestamp * 1000) return date.toLocaleDateString('zh-CN', { @@ -2091,6 +2364,40 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h } }, [isImage, imageCacheKey, message.imageDatName, message.imageMd5]) + // 引用图片自动解密 + useEffect(() => { + if (!message.quotedImageMd5) return + if (quotedImageLocalPath) return + + const doDecrypt = async () => { + try { + // 先尝试从缓存获取 + const cached = await window.electronAPI.image.resolveCache({ + sessionId: session.username, + imageMd5: message.quotedImageMd5 + }) + if (cached.success && cached.localPath) { + imageDataUrlCache.set(message.quotedImageMd5!, cached.localPath) + setQuotedImageLocalPath(cached.localPath) + return + } + + // 自动解密 + const result = await window.electronAPI.image.decrypt({ + sessionId: session.username, + imageMd5: message.quotedImageMd5, + force: false + }) + if (result.success && result.localPath) { + imageDataUrlCache.set(message.quotedImageMd5!, result.localPath) + setQuotedImageLocalPath(result.localPath) + } + } catch { } + } + + enqueueDecrypt(doDecrypt) + }, [message.quotedImageMd5, quotedImageLocalPath, session.username]) + if (isSystem) { // 系统类消息:包含“拍一拍”等 appmsg(type=62) let systemText = message.parsedContent || '[系统消息]' @@ -2135,8 +2442,25 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h return ( <div className="bubble-content"> <div className="quoted-message"> - {message.quotedSender && <span className="quoted-sender">{message.quotedSender}</span>} - <span className="quoted-text">{message.quotedContent}</span> + <div className="quoted-message-content"> + <div className="quoted-text-container"> + {message.quotedSender && <span className="quoted-sender">{message.quotedSender}</span>} + <span className="quoted-text">{message.quotedContent}</span> + </div> + {quotedImageLocalPath && ( + <div className="quoted-image-container"> + <img + src={quotedImageLocalPath} + alt="引用图片" + className="quoted-image-thumb" + onClick={(e) => { + e.stopPropagation() + window.electronAPI.window.openImageViewerWindow(quotedImageLocalPath) + }} + /> + </div> + )} + </div> </div> <div className="message-text"><MessageContent content={message.parsedContent} /></div> </div> @@ -2500,6 +2824,66 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h ) } + // 聊天记录 (type=19) + if (appMsgType === '19') { + const recordList = message.chatRecordList || [] + const displayTitle = title || '群聊的聊天记录' + const metaText = + recordList.length > 0 + ? `共 ${recordList.length} 条聊天记录` + : desc || '聊天记录' + + const previewItems = recordList.slice(0, 4) + + return ( + <div + className="link-message chat-record-message" + onClick={(e) => { + e.stopPropagation() + window.electronAPI.window.openChatHistoryWindow(session.username, message.localId) + }} + title="点击查看详细聊天记录" + > + <div className="link-header"> + <div className="link-title" title={displayTitle}> + {displayTitle} + </div> + </div> + <div className="link-body"> + <div className="chat-record-preview"> + {previewItems.length > 0 ? ( + <> + <div className="chat-record-meta-line" title={metaText}> + {metaText} + </div> + <div className="chat-record-list"> + {previewItems.map((item, i) => ( + <div key={i} className="chat-record-item"> + <span className="source-name"> + {item.sourcename ? `${item.sourcename}: ` : ''} + </span> + {item.datadesc || item.datatitle || '[媒体消息]'} + </div> + ))} + {recordList.length > previewItems.length && ( + <div className="chat-record-more">还有 {recordList.length - previewItems.length} 条…</div> + )} + </div> + </> + ) : ( + <div className="chat-record-desc"> + {desc || '点击打开查看完整聊天记录'} + </div> + )} + </div> + <div className="chat-record-icon"> + <MessageSquare size={18} /> + </div> + </div> + </div> + ) + } + // 文件消息 (type=6):渲染为文件卡片 if (appMsgType === '6') { // 优先使用从接口获取的文件信息,否则从 XML 解析 @@ -2544,14 +2928,14 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h } const wxid = userInfo.userInfo.wxid - + // 文件存储在 {微信存储目录}\{账号文件夹}\msg\file\{年-月}\ 目录下 // 根据消息创建时间计算日期目录 const msgDate = new Date(message.createTime * 1000) const year = msgDate.getFullYear() const month = String(msgDate.getMonth() + 1).padStart(2, '0') const dateFolder = `${year}-${month}` - + // 构建完整文件路径(包括文件名) const filePath = `${wechatDir}\\${wxid}\\msg\\file\\${dateFolder}\\${fileName}` @@ -2563,7 +2947,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h console.warn('无法定位到具体文件,尝试打开文件夹:', err) const fileDir = `${wechatDir}\\${wxid}\\msg\\file\\${dateFolder}` const result = await window.electronAPI.shell.openPath(fileDir) - + // 如果还是失败,打开上级目录 if (result) { console.warn('无法打开月份文件夹,尝试打开上级目录') @@ -2577,8 +2961,8 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h } return ( - <div - className="file-message" + <div + className="file-message" onClick={handleFileClick} style={{ cursor: 'pointer' }} title="点击定位到文件所在文件夹" diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 14e394a..d1f8bd6 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -104,6 +104,7 @@ function SettingsPage() { const [skipIntegrityCheck, setSkipIntegrityCheck] = useState(false) const [exportDefaultDateRange, setExportDefaultDateRange] = useState<number>(0) const [exportDefaultAvatars, setExportDefaultAvatars] = useState<boolean>(true) + const [autoUpdateDatabase, setAutoUpdateDatabase] = useState(true) // AI 相关配置状态 const [aiProvider, setAiProviderState] = useState('zhipu') @@ -142,6 +143,7 @@ function SettingsPage() { const savedSttLanguages = await configService.getSttLanguages() const savedSttModelType = await configService.getSttModelType() const savedSkipIntegrityCheck = await configService.getSkipIntegrityCheck() + const savedAutoUpdateDatabase = await configService.getAutoUpdateDatabase() if (savedKey) setDecryptKey(savedKey) if (savedPath) setDbPath(savedPath) @@ -157,6 +159,7 @@ function SettingsPage() { } setSttModelType(savedSttModelType) setSkipIntegrityCheck(savedSkipIntegrityCheck) + setAutoUpdateDatabase(savedAutoUpdateDatabase) const savedQuoteStyle = await configService.getQuoteStyle() setQuoteStyle(savedQuoteStyle) @@ -648,6 +651,8 @@ function SettingsPage() { // 保存完整性检查设置 await configService.setSkipIntegrityCheck(skipIntegrityCheck) + // 保存自动更新设置 + await configService.setAutoUpdateDatabase(autoUpdateDatabase) // 保存引用样式 await configService.setQuoteStyle(quoteStyle) @@ -767,7 +772,28 @@ function SettingsPage() { </div> {/* 数据库解密部分 */} - <h3 className="section-title">数据库解密(已支持自动更新)</h3> + <h3 className="section-title">数据库解密与同步</h3> + + <div className="form-group"> + <div className="toggle-setting"> + <div className="toggle-header"> + <label className="toggle-label"> + <span className="toggle-title">开启数据库自动增量同步</span> + <div className="toggle-switch"> + <input + type="checkbox" + checked={autoUpdateDatabase} + onChange={(e) => setAutoUpdateDatabase(e.target.checked)} + /> + <span className="toggle-slider" /> + </div> + </label> + </div> + <div className="toggle-description"> + <p>当检测到微信数据库文件变化时(如收到新消息),自动将新数据同步到密语。</p> + </div> + </div> + </div> <div className="form-group"> <label>解密密钥</label> diff --git a/src/services/config.ts b/src/services/config.ts index 337dc25..93df769 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -19,12 +19,29 @@ export const CONFIG_KEYS = { QUOTE_STYLE: 'quoteStyle', SKIP_INTEGRITY_CHECK: 'skipIntegrityCheck', EXPORT_DEFAULT_DATE_RANGE: 'exportDefaultDateRange', - EXPORT_DEFAULT_AVATARS: 'exportDefaultAvatars' + EXPORT_DEFAULT_AVATARS: 'exportDefaultAvatars', + AUTO_UPDATE_DATABASE: 'autoUpdateDatabase' } as const // 当前协议版本 - 更新协议内容时递增此版本号 export const CURRENT_AGREEMENT_VERSION = 2 +// ... existing code ... + +// 获取是否自动更新数据库 +export async function getAutoUpdateDatabase(): Promise<boolean> { + const value = await config.get(CONFIG_KEYS.AUTO_UPDATE_DATABASE) + return value !== undefined ? (value as boolean) : true +} + +// 设置是否自动更新数据库 +export async function setAutoUpdateDatabase(enable: boolean): Promise<void> { + await config.set(CONFIG_KEYS.AUTO_UPDATE_DATABASE, enable) +} + + +// --- AI 摘要配置 --- + // 获取解密密钥 export async function getDecryptKey(): Promise<string | null> { const value = await config.get(CONFIG_KEYS.DECRYPT_KEY) diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index b480009..8856ad8 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -23,6 +23,7 @@ export interface ElectronAPI { openBrowserWindow: (url: string, title?: string) => Promise<void> resizeToFitVideo: (videoWidth: number, videoHeight: number) => Promise<void> openAISummaryWindow: (sessionId: string, sessionName: string) => Promise<boolean> + openChatHistoryWindow: (sessionId: string, messageId: number) => Promise<boolean> } config: { get: (key: string) => Promise<unknown> @@ -226,6 +227,19 @@ export interface ElectronAPI { data?: string // base64 encoded WAV error?: string }> + getMessagesByDate: (sessionId: string, targetTimestamp: number, limit?: number) => Promise<{ + success: boolean + messages?: Message[] + targetIndex?: number + targetIndex?: number + error?: string + }> + getMessage: (sessionId: string, localId: number) => Promise<{ success: boolean; message?: Message; error?: string }> + getDatesWithMessages: (sessionId: string, year: number, month: number) => Promise<{ + success: boolean + dates?: string[] + error?: string + }> onSessionsUpdated: (callback: (sessions: ChatSession[]) => void) => () => void } analytics: { diff --git a/src/types/models.ts b/src/types/models.ts index 7be2b9e..c8108a2 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -51,6 +51,7 @@ export interface Message { // 引用消息 quotedContent?: string quotedSender?: string + quotedImageMd5?: string // 视频相关 videoMd5?: string rawContent?: string @@ -60,6 +61,30 @@ export interface Message { fileSize?: number // 文件大小(字节) fileExt?: string // 文件扩展名 fileMd5?: string // 文件 MD5 + chatRecordList?: ChatRecordItem[] // 聊天记录列表 (Type 19) +} + +export interface ChatRecordItem { + datatype: number + datadesc?: string + datatitle?: string + sourcename?: string + sourcetime?: string + sourceheadurl?: string + fileext?: string + datasize?: number + messageuuid?: string + // 媒体信息 + dataurl?: string // 原始地址 + datathumburl?: string // 缩略图地址 + datacdnurl?: string // CDN地址 + qaeskey?: string // AES Key (通常在 recorditem 中是 qaeskey 或 aeskey) + aeskey?: string + md5?: string + imgheight?: number + imgwidth?: number + thumbheadurl?: string // 视频/图片缩略图 + duration?: number // 语音/视频时长 } // 分析数据