mirror of
https://github.com/ILoveBingLu/CipherTalk.git
synced 2026-05-23 04:53:24 +08:00
feat: 更新 AI 模型列表和导出功能优化
- 更新版本号至 2.2.5 - 豆包提供商:新增模型映射,支持中文显示名称(豆包2.0系列、Seed系列等) - Gemini 提供商:新增 Gemini 3.1 Pro 模型支持 - Kimi 提供商:新增模型映射,支持中文显示名称(Kimi 2.5、K2系列、Moonshot系列) - 智谱AI 提供商:新增 GLM-5 模型支持 - 导出服务:移除 exportFiles 选项,简化文件导出逻辑 - HTML 导出生成器:优化文件处理和媒体路径映射 - 更新 WhatsNewModal 和 ExportPage 组件以适配新功能 - 更新 Electron 类型定义以支持新的导出选项
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
**一款现代化的微信聊天记录查看与分析工具**
|
||||
|
||||
[](LICENSE)
|
||||
[](package.json)
|
||||
[](package.json)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
@@ -8,7 +8,7 @@ export const DoubaoMetadata = {
|
||||
name: 'doubao',
|
||||
displayName: '豆包',
|
||||
description: '字节跳动出品,响应快速',
|
||||
models: ['doubao-seed-1-8-251228', 'doubao-seed-1-6-251015', 'doubao-seed-1-6-lite-251015', 'doubao-seed-1-6-flash-250828', 'deepseek-v3-2-251201', 'glm-4-7-251222', 'doubao-1-5-pro-32k-250115'],
|
||||
models: ['豆包2.0 Pro', '豆包2.0 Lite', '豆包2.0 Mini', '豆包Seed 1.8', '豆包Seed 1.6', '豆包Seed 1.6 Lite', '豆包Seed 1.6 Flash', 'DeepSeek V3.2', 'GLM-4.7', '豆包1.5 Pro 32K'],
|
||||
pricing: '¥0.008/1K tokens',
|
||||
pricingDetail: {
|
||||
input: 0.008, // 0.008元/1K tokens
|
||||
@@ -18,6 +18,19 @@ export const DoubaoMetadata = {
|
||||
logo: './AI-logo/doubao-color.svg'
|
||||
}
|
||||
|
||||
const MODEL_MAPPING: Record<string, string> = {
|
||||
'豆包2.0 Pro': 'doubao-seed-2-0-pro-260215',
|
||||
'豆包2.0 Lite': 'doubao-seed-2-0-lite-260215',
|
||||
'豆包2.0 Mini': 'doubao-seed-2-0-mini-260215',
|
||||
'豆包Seed 1.8': 'doubao-seed-1-8-251228',
|
||||
'豆包Seed 1.6': 'doubao-seed-1-6-251015',
|
||||
'豆包Seed 1.6 Lite': 'doubao-seed-1-6-lite-251015',
|
||||
'豆包Seed 1.6 Flash': 'doubao-seed-1-6-flash-250828',
|
||||
'DeepSeek V3.2': 'deepseek-v3-2-251201',
|
||||
'GLM-4.7': 'glm-4-7-251222',
|
||||
'豆包1.5 Pro 32K': 'doubao-1-5-pro-32k-250115'
|
||||
}
|
||||
|
||||
/**
|
||||
* 豆包提供商
|
||||
*/
|
||||
@@ -30,4 +43,18 @@ export class DoubaoProvider extends BaseAIProvider {
|
||||
constructor(apiKey: string) {
|
||||
super(apiKey, 'https://ark.cn-beijing.volces.com/api/v3')
|
||||
}
|
||||
|
||||
private getModelId(displayName: string): string {
|
||||
return MODEL_MAPPING[displayName] || displayName
|
||||
}
|
||||
|
||||
async chat(messages: any[], options?: any): Promise<string> {
|
||||
const modelId = this.getModelId(options?.model || this.models[0])
|
||||
return super.chat(messages, { ...options, model: modelId })
|
||||
}
|
||||
|
||||
async streamChat(messages: any[], options: any, onChunk: (chunk: string) => void): Promise<void> {
|
||||
const modelId = this.getModelId(options?.model || this.models[0])
|
||||
return super.streamChat(messages, { ...options, model: modelId }, onChunk)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export const GeminiMetadata = {
|
||||
displayName: 'Gemini',
|
||||
description: 'Google 最新的多模态 AI 模型',
|
||||
models: [
|
||||
'Gemini 3.1 Pro',
|
||||
'Gemini 3 Pro Preview',
|
||||
'Gemini 3 Flash Preview',
|
||||
'Gemini 2.5 Flash',
|
||||
@@ -28,6 +29,7 @@ export const GeminiMetadata = {
|
||||
}
|
||||
|
||||
const MODEL_MAPPING: Record<string, string> = {
|
||||
'Gemini 3.1 Pro': 'gemini-3.1-pro-preview',
|
||||
'Gemini 3 Pro Preview': 'gemini-3-pro-preview',
|
||||
'Gemini 3 Flash Preview': 'gemini-3-flash-preview',
|
||||
'Gemini 2.5 Flash': 'gemini-2.5-flash',
|
||||
|
||||
@@ -8,7 +8,7 @@ export const KimiMetadata = {
|
||||
name: 'kimi',
|
||||
displayName: 'Kimi',
|
||||
description: '支持超长上下文',
|
||||
models: ['kimi-k2-0711-preview', 'kimi-k2-0905-preview', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo', 'kimi-k2-turbo-preview', 'kimi-k2-turbo', 'kimi-latest', 'moonshot-v1-128k', 'moonshot-v1-32k', 'moonshot-v1-8k', 'moonshot-v1-8k-flash', 'moonshot-v1-auto'],
|
||||
models: ['Kimi 2.5', 'Kimi K2 (0711)', 'Kimi K2 (0905)', 'Kimi K2 Thinking', 'Kimi K2 Thinking Turbo', 'Kimi K2 Turbo Preview', 'Kimi K2 Turbo', 'Kimi Latest', 'Moonshot 128K', 'Moonshot 32K', 'Moonshot 8K', 'Moonshot 8K Flash', 'Moonshot Auto'],
|
||||
pricing: '¥0.012/1K tokens',
|
||||
pricingDetail: {
|
||||
input: 0.012, // 0.012元/1K tokens
|
||||
@@ -18,6 +18,22 @@ export const KimiMetadata = {
|
||||
logo: './AI-logo/kimi-color.svg'
|
||||
}
|
||||
|
||||
const MODEL_MAPPING: Record<string, string> = {
|
||||
'Kimi 2.5': 'kimi-k2.5',
|
||||
'Kimi K2 (0711)': 'kimi-k2-0711-preview',
|
||||
'Kimi K2 (0905)': 'kimi-k2-0905-preview',
|
||||
'Kimi K2 Thinking': 'kimi-k2-thinking',
|
||||
'Kimi K2 Thinking Turbo': 'kimi-k2-thinking-turbo',
|
||||
'Kimi K2 Turbo Preview': 'kimi-k2-turbo-preview',
|
||||
'Kimi K2 Turbo': 'kimi-k2-turbo',
|
||||
'Kimi Latest': 'kimi-latest',
|
||||
'Moonshot 128K': 'moonshot-v1-128k',
|
||||
'Moonshot 32K': 'moonshot-v1-32k',
|
||||
'Moonshot 8K': 'moonshot-v1-8k',
|
||||
'Moonshot 8K Flash': 'moonshot-v1-8k-flash',
|
||||
'Moonshot Auto': 'moonshot-v1-auto'
|
||||
}
|
||||
|
||||
/**
|
||||
* Kimi提供商
|
||||
*/
|
||||
@@ -30,4 +46,18 @@ export class KimiProvider extends BaseAIProvider {
|
||||
constructor(apiKey: string) {
|
||||
super(apiKey, 'https://api.moonshot.cn/v1')
|
||||
}
|
||||
|
||||
private getModelId(displayName: string): string {
|
||||
return MODEL_MAPPING[displayName] || displayName
|
||||
}
|
||||
|
||||
async chat(messages: any[], options?: any): Promise<string> {
|
||||
const modelId = this.getModelId(options?.model || this.models[0])
|
||||
return super.chat(messages, { ...options, model: modelId })
|
||||
}
|
||||
|
||||
async streamChat(messages: any[], options: any, onChunk: (chunk: string) => void): Promise<void> {
|
||||
const modelId = this.getModelId(options?.model || this.models[0])
|
||||
return super.streamChat(messages, { ...options, model: modelId }, onChunk)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const ZhipuMetadata = {
|
||||
displayName: '智谱AI',
|
||||
description: '国产大模型,性能优秀',
|
||||
models: [
|
||||
'GLM-5',
|
||||
'GLM-4.7 Flash',
|
||||
'GLM-4.6v Flash',
|
||||
'GLM-4.5 Flash',
|
||||
@@ -26,6 +27,7 @@ export const ZhipuMetadata = {
|
||||
}
|
||||
|
||||
const MODEL_MAPPING: Record<string, string> = {
|
||||
'GLM-5': 'glm-5',
|
||||
'GLM-4.7 Flash': 'glm-4.7-flash',
|
||||
'GLM-4.6v Flash': 'glm-4.6v-flash',
|
||||
'GLM-4.5 Flash': 'glm-4.5-flash',
|
||||
|
||||
@@ -92,7 +92,6 @@ export interface ExportOptions {
|
||||
exportVideos?: boolean
|
||||
exportEmojis?: boolean
|
||||
exportVoices?: boolean
|
||||
exportFiles?: boolean
|
||||
mediaPathMap?: Map<number, string>
|
||||
}
|
||||
|
||||
@@ -594,12 +593,7 @@ class ExportService {
|
||||
return '[转账]'
|
||||
}
|
||||
|
||||
if (type === '6') {
|
||||
if (mediaPathMap && createTime && mediaPathMap.has(createTime)) {
|
||||
return `[文件] ${mediaPathMap.get(createTime)} ${title || ''}`
|
||||
}
|
||||
return title ? `[文件] ${title}` : '[文件]'
|
||||
}
|
||||
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 || '[引用消息]'
|
||||
@@ -638,12 +632,7 @@ class ExportService {
|
||||
}
|
||||
|
||||
// 其他类型
|
||||
if (xmlType === '6') {
|
||||
if (mediaPathMap && createTime && mediaPathMap.has(createTime)) {
|
||||
return `[文件] ${mediaPathMap.get(createTime)} ${title || ''}`
|
||||
}
|
||||
return title ? `[文件] ${title}` : '[文件]'
|
||||
}
|
||||
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 || '[引用消息]'
|
||||
@@ -2168,7 +2157,7 @@ class ExportService {
|
||||
else if (options.format === 'html') ext = '.html'
|
||||
|
||||
// 当导出媒体时,创建会话子文件夹,把文件和媒体都放进去
|
||||
const hasMedia = options.exportImages || options.exportVideos || options.exportEmojis || options.exportVoices || options.exportFiles
|
||||
const hasMedia = options.exportImages || options.exportVideos || options.exportEmojis || options.exportVoices
|
||||
const sessionOutputDir = hasMedia ? path.join(outputDir, safeName) : outputDir
|
||||
if (hasMedia && !fs.existsSync(sessionOutputDir)) {
|
||||
fs.mkdirSync(sessionOutputDir, { recursive: true })
|
||||
@@ -2257,7 +2246,6 @@ class ExportService {
|
||||
const imageOutDir = options.exportImages ? path.join(outputDir, 'images') : ''
|
||||
const videoOutDir = options.exportVideos ? path.join(outputDir, 'videos') : ''
|
||||
const emojiOutDir = options.exportEmojis ? path.join(outputDir, 'emojis') : ''
|
||||
const fileOutDir = options.exportFiles ? path.join(outputDir, 'files') : ''
|
||||
|
||||
if (options.exportImages && !fs.existsSync(imageOutDir)) {
|
||||
fs.mkdirSync(imageOutDir, { recursive: true })
|
||||
@@ -2268,14 +2256,10 @@ class ExportService {
|
||||
if (options.exportEmojis && !fs.existsSync(emojiOutDir)) {
|
||||
fs.mkdirSync(emojiOutDir, { recursive: true })
|
||||
}
|
||||
if (options.exportFiles && !fs.existsSync(fileOutDir)) {
|
||||
fs.mkdirSync(fileOutDir, { recursive: true })
|
||||
}
|
||||
|
||||
let imageCount = 0
|
||||
let videoCount = 0
|
||||
let emojiCount = 0
|
||||
let fileCount = 0
|
||||
let emojiTotal = 0
|
||||
let emojiProcessed = 0
|
||||
|
||||
@@ -2357,12 +2341,14 @@ class ExportService {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const ext = path.extname(filePath) || '.jpg'
|
||||
const fileName = `${createTime}_${imageMd5 || imageDatName}${ext}`
|
||||
const destPath = path.join(imageOutDir, fileName)
|
||||
const df = this.dateFolder(createTime)
|
||||
const dayDir = path.join(imageOutDir, df)
|
||||
if (!fs.existsSync(dayDir)) fs.mkdirSync(dayDir, { recursive: true })
|
||||
const destPath = path.join(dayDir, fileName)
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.copyFileSync(filePath, destPath)
|
||||
imageCount++
|
||||
// 记录映射:createTime → 相对路径
|
||||
mediaPathMap.set(createTime, `images/${fileName}`)
|
||||
mediaPathMap.set(createTime, `images/${df}/${fileName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2382,12 +2368,14 @@ class ExportService {
|
||||
const videoPath = videoInfo.videoUrl.replace(/^file:\/\/\//i, '').replace(/\//g, path.sep)
|
||||
if (fs.existsSync(videoPath)) {
|
||||
const fileName = `${createTime}_${videoMd5}.mp4`
|
||||
const destPath = path.join(videoOutDir, fileName)
|
||||
const df = this.dateFolder(createTime)
|
||||
const dayDir = path.join(videoOutDir, df)
|
||||
if (!fs.existsSync(dayDir)) fs.mkdirSync(dayDir, { recursive: true })
|
||||
const destPath = path.join(dayDir, fileName)
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.copyFileSync(videoPath, destPath)
|
||||
videoCount++
|
||||
// 记录映射:createTime → 相对路径
|
||||
mediaPathMap.set(createTime, `videos/${fileName}`)
|
||||
mediaPathMap.set(createTime, `videos/${df}/${fileName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2423,7 +2411,10 @@ class ExportService {
|
||||
// 确定文件扩展名
|
||||
const ext = cdnUrl.includes('.gif') || content.includes('type="2"') ? '.gif' : '.png'
|
||||
const fileName = `${createTime}_${cacheKey}${ext}`
|
||||
const destPath = path.join(emojiOutDir, fileName)
|
||||
const df = this.dateFolder(createTime)
|
||||
const dayDir = path.join(emojiOutDir, df)
|
||||
if (!fs.existsSync(dayDir)) fs.mkdirSync(dayDir, { recursive: true })
|
||||
const destPath = path.join(dayDir, fileName)
|
||||
|
||||
if (!fs.existsSync(destPath)) {
|
||||
// 1. 先检查本地缓存(cachePath/Emojis/)
|
||||
@@ -2442,11 +2433,10 @@ class ExportService {
|
||||
if (sourceFile && fs.existsSync(sourceFile)) {
|
||||
fs.copyFileSync(sourceFile, destPath)
|
||||
emojiCount++
|
||||
mediaPathMap.set(createTime, `emojis/${fileName}`)
|
||||
mediaPathMap.set(createTime, `emojis/${df}/${fileName}`)
|
||||
}
|
||||
} else {
|
||||
// 文件已存在,只记录映射
|
||||
mediaPathMap.set(createTime, `emojis/${fileName}`)
|
||||
mediaPathMap.set(createTime, `emojis/${df}/${fileName}`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -2544,11 +2534,14 @@ class ExportService {
|
||||
for (let idx = 0; idx < total; idx++) {
|
||||
const createTime = voiceCreateTimes[idx]
|
||||
const fileName = `${createTime}.wav`
|
||||
const destPath = path.join(voiceOutDir, fileName)
|
||||
const df = this.dateFolder(createTime)
|
||||
const dayDir = path.join(voiceOutDir, df)
|
||||
if (!fs.existsSync(dayDir)) fs.mkdirSync(dayDir, { recursive: true })
|
||||
const destPath = path.join(dayDir, fileName)
|
||||
|
||||
// 已存在则跳过
|
||||
if (fs.existsSync(destPath)) {
|
||||
mediaPathMap.set(createTime, `voices/${fileName}`)
|
||||
mediaPathMap.set(createTime, `voices/${df}/${fileName}`)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -2591,7 +2584,7 @@ class ExportService {
|
||||
const wavData = this.createWavBuffer(pcmData, 24000)
|
||||
fs.writeFileSync(destPath, wavData)
|
||||
voiceCount++
|
||||
mediaPathMap.set(createTime, `voices/${fileName}`)
|
||||
mediaPathMap.set(createTime, `voices/${df}/${fileName}`)
|
||||
} catch { }
|
||||
|
||||
// 进度日志
|
||||
@@ -2609,70 +2602,25 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
// === 文件导出(独立流程:从微信原始目录复制) ===
|
||||
if (options.exportFiles) {
|
||||
onDetail?.('正在导出文件...')
|
||||
const wechatDir = this.configService.get('dbPath') as string
|
||||
const wxid = this.configService.get('myWxid') as string
|
||||
|
||||
if (wechatDir && wxid) {
|
||||
const accountDir = this.findAccountDir(wechatDir, wxid)
|
||||
const fileBaseDir = path.join(wechatDir, accountDir || wxid, 'msg', 'file')
|
||||
|
||||
for (const { db, tableName } of dbTablePairs) {
|
||||
try {
|
||||
let sql = `SELECT * FROM ${tableName} WHERE local_type = 49`
|
||||
if (options.dateRange) {
|
||||
sql += ` AND create_time >= ${options.dateRange.start} AND create_time <= ${options.dateRange.end}`
|
||||
}
|
||||
sql += ` ORDER BY create_time ASC`
|
||||
const rows = db.prepare(sql).all() as any[]
|
||||
|
||||
for (const row of rows) {
|
||||
try {
|
||||
const createTime = row.create_time || 0
|
||||
const content = this.decodeMessageContent(row.message_content, row.compress_content)
|
||||
const xmlType = this.extractXmlValue(content, 'type')
|
||||
if (xmlType !== '6') continue
|
||||
|
||||
const fileName = this.extractXmlValue(content, 'title')
|
||||
if (!fileName) continue
|
||||
|
||||
// 根据消息时间计算年月目录
|
||||
const msgDate = new Date(createTime * 1000)
|
||||
const dateFolder = `${msgDate.getFullYear()}-${String(msgDate.getMonth() + 1).padStart(2, '0')}`
|
||||
const srcPath = path.join(fileBaseDir, dateFolder, fileName)
|
||||
|
||||
if (fs.existsSync(srcPath)) {
|
||||
const safeFileName = `${createTime}_${fileName}`
|
||||
const destPath = path.join(fileOutDir, safeFileName)
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.copyFileSync(srcPath, destPath)
|
||||
fileCount++
|
||||
}
|
||||
mediaPathMap.set(createTime, `files/${safeFileName}`)
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Export] 读取文件消息失败:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parts: string[] = []
|
||||
if (imageCount > 0) parts.push(`${imageCount} 张图片`)
|
||||
if (videoCount > 0) parts.push(`${videoCount} 个视频`)
|
||||
if (emojiCount > 0) parts.push(`${emojiCount} 个表情`)
|
||||
if (voiceCount > 0) parts.push(`${voiceCount} 条语音`)
|
||||
if (fileCount > 0) parts.push(`${fileCount} 个文件`)
|
||||
const summary = parts.length > 0 ? `媒体导出完成: ${parts.join(', ')}` : '无媒体文件'
|
||||
onDetail?.(summary)
|
||||
console.log(`[Export] ${sessionId} ${summary}`)
|
||||
return mediaPathMap
|
||||
}
|
||||
|
||||
private dateFolder(ts: number): string {
|
||||
const d = new Date(ts * 1000)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}${m}${day}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库行的 packed_info_data 中解析图片 dat 文件名
|
||||
* 复制自 chatService.parseImageDatNameFromRow 逻辑
|
||||
|
||||
@@ -471,19 +471,6 @@ body {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 文件链接 */
|
||||
.msg-file {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
.msg-file:hover { opacity: 0.8; }
|
||||
|
||||
/* 聊天记录引用 */
|
||||
.chat-records {
|
||||
margin-top: 4px;
|
||||
@@ -759,14 +746,6 @@ body {
|
||||
}
|
||||
if (content === '[语音消息]') return '<div class="msg-image broken">🎙️ 语音</div>';
|
||||
|
||||
// 文件消息:[文件] files/xxx.pdf 原始文件名
|
||||
const fileMatch = content.match(/^\[文件\]\s+(files\/[^\s]+)\s*(.*)$/);
|
||||
if (fileMatch) {
|
||||
const src = fileMatch[1];
|
||||
const name = fileMatch[2] || src.replace(/^files\/\d+_/, '');
|
||||
return '<a class="msg-file" href="' + esc(src) + '" target="_blank">📎 ' + esc(name) + '</a>';
|
||||
}
|
||||
|
||||
return '<span class="msg-text">' + esc(content) + '</span>';
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ciphertalk",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.5",
|
||||
"description": "密语 - 微信聊天记录查看工具",
|
||||
"author": "ILoveBingLu",
|
||||
"license": "CC-BY-NC-SA-4.0",
|
||||
|
||||
@@ -8,16 +8,16 @@ interface WhatsNewModalProps {
|
||||
|
||||
function WhatsNewModal({ onClose, version }: WhatsNewModalProps) {
|
||||
const updates = [
|
||||
// {
|
||||
// icon: <Package size={20} />,
|
||||
// title: '媒体导出',
|
||||
// desc: '导出聊天记录时可同时导出图片、视频、表情包和语音消息。'
|
||||
// },
|
||||
// {
|
||||
// icon: <Image size={20} />,
|
||||
// title: '图片自动解密',
|
||||
// desc: '导出时自动解密未缓存的图片,无需提前在密语聊天窗口浏览。'
|
||||
// },
|
||||
{
|
||||
icon: <Package size={20} />,
|
||||
title: '优化',
|
||||
desc: '修复并优化部分内容。'
|
||||
},
|
||||
{
|
||||
icon: <Image size={20} />,
|
||||
title: '聊天内图片',
|
||||
desc: '支持查看谷歌标准实况图片(iOS端与大疆等实况图片,发送后实况暂不支持)。'
|
||||
}
|
||||
// {
|
||||
// icon: <Mic size={20} />,
|
||||
// title: '语音导出',
|
||||
@@ -28,11 +28,11 @@ function WhatsNewModal({ onClose, version }: WhatsNewModalProps) {
|
||||
// title: '分类导出',
|
||||
// desc: '导出时可按群聊或个人聊天筛选,支持日期范围过滤。'
|
||||
// }
|
||||
{
|
||||
icon: <Aperture size={20} />,
|
||||
title: '朋友圈',
|
||||
desc: '新增朋友圈功能!'
|
||||
}
|
||||
// {
|
||||
// icon: <Aperture size={20} />,
|
||||
// title: '朋友圈',
|
||||
// desc: '新增朋友圈功能!'
|
||||
// }
|
||||
]
|
||||
|
||||
const handleTelegram = () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Search, Download, FolderOpen, RefreshCw, Check, FileJson, FileText, Table, Loader2, X, FileSpreadsheet, Database, FileCode, CheckCircle, XCircle, ExternalLink, MessageSquare, Users, User, Filter, Image, Video, CircleUserRound, Smile, Mic, Paperclip } from 'lucide-react'
|
||||
import { Search, Download, FolderOpen, RefreshCw, Check, FileJson, FileText, Table, Loader2, X, FileSpreadsheet, Database, FileCode, CheckCircle, XCircle, ExternalLink, MessageSquare, Users, User, Filter, Image, Video, CircleUserRound, Smile, Mic } from 'lucide-react'
|
||||
import DateRangePicker from '../components/DateRangePicker'
|
||||
import { useTitleBarStore } from '../stores/titleBarStore'
|
||||
import * as configService from '../services/config'
|
||||
@@ -33,7 +33,6 @@ interface ExportOptions {
|
||||
exportVideos: boolean
|
||||
exportEmojis: boolean
|
||||
exportVoices: boolean
|
||||
exportFiles: boolean
|
||||
}
|
||||
|
||||
interface ContactExportOptions {
|
||||
@@ -87,8 +86,7 @@ function ExportPage() {
|
||||
exportImages: false,
|
||||
exportVideos: false,
|
||||
exportEmojis: false,
|
||||
exportVoices: false,
|
||||
exportFiles: false
|
||||
exportVoices: false
|
||||
})
|
||||
|
||||
// 通讯录导出状态
|
||||
@@ -428,8 +426,7 @@ function ExportPage() {
|
||||
exportImages: options.exportImages,
|
||||
exportVideos: options.exportVideos,
|
||||
exportEmojis: options.exportEmojis,
|
||||
exportVoices: options.exportVoices,
|
||||
exportFiles: options.exportFiles
|
||||
exportVoices: options.exportVoices
|
||||
}
|
||||
|
||||
if (options.format === 'chatlab' || options.format === 'chatlab-jsonl' || options.format === 'json' || options.format === 'excel' || options.format === 'html') {
|
||||
@@ -706,16 +703,6 @@ function ExportPage() {
|
||||
<Mic size={16} style={{ color: 'var(--text-tertiary)' }} />
|
||||
<span>导出语音</span>
|
||||
</label>
|
||||
<label className="checkbox-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={options.exportFiles}
|
||||
onChange={e => setOptions(prev => ({ ...prev, exportFiles: e.target.checked }))}
|
||||
/>
|
||||
<div className="custom-checkbox"></div>
|
||||
<Paperclip size={16} style={{ color: 'var(--text-tertiary)' }} />
|
||||
<span>导出文件</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -973,7 +960,6 @@ function ExportPage() {
|
||||
{options.exportVideos && <span> · 含视频</span>}
|
||||
{options.exportEmojis && <span> · 含表情</span>}
|
||||
{options.exportVoices && <span> · 含语音</span>}
|
||||
{options.exportFiles && <span> · 含文件</span>}
|
||||
{options.exportAvatars && <span> · 含头像</span>}
|
||||
</div>
|
||||
{exportProgress.total > 0 && (
|
||||
|
||||
Vendored
-1
@@ -727,7 +727,6 @@ export interface ExportOptions {
|
||||
dateRange?: { start: number; end: number } | null
|
||||
exportMedia?: boolean
|
||||
exportAvatars?: boolean
|
||||
exportFiles?: boolean
|
||||
}
|
||||
|
||||
export interface ContactExportOptions {
|
||||
|
||||
Reference in New Issue
Block a user