mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-18 03:57:02 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a740a50e79 | ||
|
|
ffe797b3e4 | ||
|
|
9cd6061c0f | ||
|
|
67d86f7834 | ||
|
|
a6138fd62b | ||
|
|
d1763757be | ||
|
|
a4dfa542d1 | ||
|
|
9fb1d14c41 | ||
|
|
eb60ce4984 | ||
|
|
061086f0ad | ||
|
|
d1cb6277f9 | ||
|
|
02baed9bb6 | ||
|
|
55b792b23c | ||
|
|
7c3fa728f7 | ||
|
|
755d11f71c | ||
|
|
eeded5fc46 | ||
|
|
ac14864655 | ||
|
|
80316b1db7 |
+3
-1
@@ -1,8 +1,10 @@
|
||||
# WeChat Database Key (Optional, can be entered in UI)
|
||||
VITE_DB_KEY=
|
||||
|
||||
# DeepSeek API Key (Optional, can be entered in UI)
|
||||
# AI API Configuration (Optional, can be entered in UI)
|
||||
VITE_DEEPSEEK_API_KEY=
|
||||
VITE_AI_BASE_URL=https://api.deepseek.com
|
||||
VITE_AI_MODEL=deepseek-chat
|
||||
|
||||
# Message types to filter out (comma separated)
|
||||
VITE_FILTER_MSG_TYPES=分享消息,图片,表情包,视频
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [x64, arm64]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: '7'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Build DMG
|
||||
run: pnpm run build:mac:${{ matrix.platform }}
|
||||
|
||||
- name: Upload DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dmg-${{ matrix.platform }}
|
||||
path: out/mac-*/WechatExplorer-*.dmg
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: dmg-*
|
||||
path: artifacts
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: artifacts/**/*
|
||||
generate_release_notes: true
|
||||
@@ -5,3 +5,4 @@ out
|
||||
.DS_Store
|
||||
.eslintcache
|
||||
*.log*
|
||||
.omc
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 WechatExplorer Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -5,88 +5,46 @@ MAC系统 获取微信聊天记录 AI一键生成群聊总结
|
||||
|
||||
## 📸 预览
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<img src="./public/WX20251215-155324.png" alt="预览图1" width="48%" />
|
||||
<img src="./public/WX20251215-155524.png" alt="预览图2" width="48%" />
|
||||
</div>
|
||||
<img src="./public/example1.png" alt="预览图1" />
|
||||
<img src="./public/example2.png" alt="预览图2" />
|
||||
<img src="./public/example3.png" alt="预览图3" />
|
||||
|
||||
## [点击这里下载](https://github.com/Wxw-Gu/WechatExplorer/releases)
|
||||
|
||||
## 📦 安装说明
|
||||
|
||||
1. 下载下方的 `xxx.dmg` 文件。
|
||||
2. 打开 DMG 并将应用拖动到 **Applications** (应用程序) 文件夹。
|
||||
3. 如果遇到“无法打开,因为开发者无法验证”的提示,请前往:
|
||||
`系统设置 -> 隐私与安全性 -> 仍要打开`。
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
- **聊天记录查看**: 浏览微信好友和群聊的聊天记录。
|
||||
- **全局搜索**: 快速搜索聊天内容。
|
||||
- **AI 智能总结**: 集成 DeepSeek AI,一键总结群聊精华内容,生成话题报告。
|
||||
- **AI 智能总结**: 支持多模型服务配置(DeepSeek/GPT-4o/Claude/Moonshot),一键总结群聊精华内容,生成话题报告。
|
||||
- **图片生成**: 将 AI 总结的内容生成精美图片,方便分享。
|
||||
- **数据导出**: 支持导出聊天记录为 CSV 文件(今日、昨日、近7天或全部)。
|
||||
- **安全隐私**: 所有数据仅在本地处理,AI 功能需自行配置 API Key。
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
- **Electron**: 跨平台桌面应用框架
|
||||
- **React**: 用于构建用户界面
|
||||
- **TypeScript**: 提供类型安全
|
||||
- **Vite**: 极速构建工具
|
||||
- **Better-SQLite3**: 高性能 SQLite 数据库操作
|
||||
- **OpenAI SDK**: 用于调用 DeepSeek API
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置要求
|
||||
### 使用前置要求
|
||||
|
||||
- 微信<=4.0 需要获取自己微信本地数据库的密码, 获取方式参考: [Mac 导出微信聊天记录](https://blog.vcvit.me/2024/08/02/mac-export-wechat-chat-records/)
|
||||
- 如果无法获取本地数据库密码 则无法使用当前项目
|
||||
- **微信版本**: 推荐使用微信 **4.0 以下**版本(4.0以上版本不支持数据库解密)
|
||||
- 微信 <= 4.0: 可正常使用,获取数据库密码方式参考:[Mac 导出微信聊天记录](https://blog.vcvit.me/2024/08/02/mac-export-wechat-chat-records/)
|
||||
- 微信 >= 4.0: 如需使用,推荐使用 [WeFlow](https://github.com/hicccc77/WeFlow)
|
||||
- 如无法获取本地数据库密码,则无法使用当前项目
|
||||
- Node.js (推荐 v16+)
|
||||
- pnpm (推荐) 或 npm
|
||||
- pnpm@7
|
||||
- 解密后的微信数据库文件 (`.db`) 和对应的密钥
|
||||
- [DeepSeek API Key](https://www.deepseek.com/) (用于 AI 总结功能)
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/your-username/WechatExplorer.git
|
||||
|
||||
# 进入目录
|
||||
cd WechatExplorer
|
||||
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
复制 `.env.example` 文件为 `.env`,并填写相关配置(可选,也可以在应用内设置):
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 开发模式运行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### 打包构建
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
pnpm build:win
|
||||
|
||||
# macOS
|
||||
pnpm build:mac
|
||||
|
||||
# Linux
|
||||
pnpm build:linux
|
||||
```
|
||||
- AI API Key(支持 OpenAI 兼容 API,可选 DeepSeek/GPT/Claude/Moonshot 等)
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
本项目仅供学习和研究使用。请勿用于非法用途。开发者不对使用本项目造成的任何后果负责。请遵守相关法律法规和微信使用协议。
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
[MIT](./LICENSE) License
|
||||
|
||||
## 🔗 参考
|
||||
|
||||
- [WechatMessageExplorer](https://github.com/svcvit/WechatMessageExplorer)
|
||||
- [WeFlow](https://github.com/hicccc77/WeFlow)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 获取当前版本号(从 package.json 读取)
|
||||
VERSION=$(jq -r '.version' package.json)
|
||||
|
||||
# 格式化为新的 tag(在版本号前加上 "v")
|
||||
TAG="v${VERSION}"
|
||||
|
||||
# 输出当前生成的 tag
|
||||
echo "生成的 tag: $TAG"
|
||||
|
||||
# 创建并推送 tag 到远程
|
||||
git tag $TAG
|
||||
git push origin $TAG
|
||||
|
||||
echo "Tag $TAG 已经推送到远程仓库!"
|
||||
+20
-3
@@ -1,8 +1,14 @@
|
||||
{
|
||||
"name": "wechatexplorer",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "mac 版本获取微信聊天记录, AI群聊总结助手",
|
||||
"keywords": ["wechat", "chat", "mac微信", "微信聊天记录", "AI群聊总结助手"],
|
||||
"keywords": [
|
||||
"wechat",
|
||||
"chat",
|
||||
"mac微信",
|
||||
"微信聊天记录",
|
||||
"AI群聊总结助手"
|
||||
],
|
||||
"author": "Qingmao",
|
||||
"main": "./out/main/index.js",
|
||||
"scripts": {
|
||||
@@ -17,7 +23,9 @@
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"build:mac": "electron-vite build && electron-builder --mac",
|
||||
"build:mac:x64": "electron-vite build && electron-builder --mac --x64",
|
||||
"build:mac:arm64": "electron-vite build && electron-builder --mac --arm64",
|
||||
"release:mac": "electron-vite build && electron-builder --mac --x64 --arm64 --publish always",
|
||||
"build:linux": "electron-vite build && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -55,5 +63,14 @@
|
||||
"electron",
|
||||
"esbuild"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 138 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 463 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 700 KiB |
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_DEEPSEEK_API_KEY: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
+21
-21
@@ -6,6 +6,17 @@ import { WechatDb, Contact, WechatMessage } from './wechat-db'
|
||||
|
||||
let wechatDb: WechatDb | null = null
|
||||
|
||||
const MSG_TYPE_DICT: Record<number, string> = {
|
||||
1: '普通文本',
|
||||
3: '图片',
|
||||
34: '语音',
|
||||
43: '视频',
|
||||
47: '表情包',
|
||||
48: '位置',
|
||||
49: '分享消息',
|
||||
10000: '系统消息'
|
||||
}
|
||||
|
||||
function createWindow(): void {
|
||||
// 创建浏览器窗口
|
||||
const mainWindow = new BrowserWindow({
|
||||
@@ -117,17 +128,6 @@ app.whenReady().then(() => {
|
||||
const groupMembers = wechatDb.getAllGroupMembers()
|
||||
|
||||
return rawMessages.map((msg: WechatMessage) => {
|
||||
const typeDict: Record<number, string> = {
|
||||
1: '普通文本',
|
||||
3: '图片',
|
||||
34: '语音',
|
||||
43: '视频',
|
||||
47: '表情包',
|
||||
48: '位置',
|
||||
49: '分享消息',
|
||||
10000: '系统消息'
|
||||
}
|
||||
|
||||
const msgType = parseInt(msg.messageType)
|
||||
const createTime = parseInt(msg.msgCreateTime)
|
||||
const date = new Date(createTime * 1000)
|
||||
@@ -161,14 +161,12 @@ app.whenReady().then(() => {
|
||||
|
||||
return {
|
||||
id: msg.mesLocalID || Math.random().toString(),
|
||||
from: msg.mesDes === '1' ? 'user' : 'assistant', // 1 通常是接收到的,0 是发送的?需要验证。Swift 说:[1: "user", 0: "assistant"]
|
||||
type: typeDict[msgType] || msg.messageType,
|
||||
from: msg.mesDes === 1 ? 'user' : 'assistant', // 1 通常是接收到的,0 是发送的?需要验证。Swift 说:[1: "user", 0: "assistant"]
|
||||
type: MSG_TYPE_DICT[msgType] || msg.messageType,
|
||||
datetime: date.toLocaleString('zh-CN', { hour12: false }),
|
||||
content: content,
|
||||
isSender: msg.mesDes === '0',
|
||||
img: img,
|
||||
name: name
|
||||
// 实际上让我们坚持我们观察到的:1=用户(其他人), 0=助手(我)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -183,22 +181,24 @@ app.whenReady().then(() => {
|
||||
async (
|
||||
_,
|
||||
messages: { role: string; content: string }[],
|
||||
options?: { apiKey?: string; model?: string }
|
||||
options?: { apiKey?: string; model?: string; baseURL?: string }
|
||||
) => {
|
||||
// @ts-ignore: vite env
|
||||
const apiKey = options?.apiKey || import.meta.env.VITE_DEEPSEEK_API_KEY
|
||||
const model = options?.model || 'deepseek-chat'
|
||||
const model = options?.model || import.meta.env.VITE_AI_MODEL || 'deepseek-chat'
|
||||
const baseURL =
|
||||
options?.baseURL || import.meta.env.VITE_AI_BASE_URL || 'https://api.deepseek.com'
|
||||
|
||||
if (!apiKey) {
|
||||
return { success: false, error: '未配置 DeepSeek API Key' }
|
||||
return { success: false, error: '未配置 API Key' }
|
||||
}
|
||||
|
||||
// 动态导入以避免如果未安装或初始类型缺失的问题
|
||||
const { OpenAI } = await import('openai')
|
||||
|
||||
const openai = new OpenAI({
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: apiKey
|
||||
baseURL,
|
||||
apiKey
|
||||
})
|
||||
try {
|
||||
const completion = await openai.chat.completions.create({
|
||||
@@ -208,7 +208,7 @@ app.whenReady().then(() => {
|
||||
})
|
||||
return { success: true, data: completion.choices[0].message.content }
|
||||
} catch (error: unknown) {
|
||||
console.error('DeepSeek API Error:', error)
|
||||
console.error('AI API Error:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: errorMessage }
|
||||
}
|
||||
|
||||
+5
-25
@@ -13,7 +13,7 @@ export interface UserContact {
|
||||
|
||||
export interface WechatMessage {
|
||||
mesLocalID: string
|
||||
mesDes: string
|
||||
mesDes: number
|
||||
messageType: string
|
||||
msgCreateTime: string
|
||||
msgContent: string
|
||||
@@ -62,7 +62,6 @@ export class WechatDb {
|
||||
}
|
||||
|
||||
private connectDb(dbPath: string): Database | null {
|
||||
// 1. 路径预检查
|
||||
const targetPath = path.resolve(dbPath)
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
console.error(`❌ 错误:数据库文件不存在于路径:${targetPath}`)
|
||||
@@ -70,45 +69,26 @@ export class WechatDb {
|
||||
}
|
||||
|
||||
try {
|
||||
// console.log(`🔌 正在尝试连接至:${path.basename(targetPath)}`)
|
||||
// console.log('路径是', targetPath)
|
||||
// 2. 初始化数据库句柄
|
||||
// 关键:使用 better-sqlite3-multiple-ciphers 的构造函数
|
||||
// verbose 选项有助于调试 Pragma 的执行情况
|
||||
const db = new DatabaseConstructor(targetPath, {
|
||||
// verbose: console.log
|
||||
})
|
||||
|
||||
// 此时 db 实例已创建,但因为未提供密钥,任何数据读取都会失败。
|
||||
// 必须立即配置加密参数。
|
||||
|
||||
// 3. 配置加密方案 (Cipher Scheme)
|
||||
// 必须显式切换到 'sqlcipher',因为该库默认可能使用 'chacha20'
|
||||
// 配置加密方案 (Cipher Scheme)
|
||||
db.pragma("cipher='sqlcipher'")
|
||||
|
||||
// 4. 配置页大小 (Page Size)
|
||||
// macOS 版 WeChat 使用 1024 字节。这必须在设置密钥前(或紧随其后)生效。
|
||||
db.pragma('cipher_page_size = 1024')
|
||||
|
||||
// 5. 配置兼容性与算法细节
|
||||
// 显式指定 SQLCipher 3 的行为
|
||||
db.pragma('legacy=3')
|
||||
db.pragma('kdf_iter = 64000')
|
||||
db.pragma('cipher_hmac_algorithm = HMAC_SHA1')
|
||||
db.pragma('cipher_kdf_algorithm = PBKDF2_HMAC_SHA1')
|
||||
|
||||
// 6. 应用密钥
|
||||
// 使用正确的 hex 语法: key = "x'HEXSTRING'"
|
||||
const processedKey = this.processRawKey(this.rawKey)
|
||||
// console.log(`🔑 应用密钥 (前6位): ${processedKey.substring(0, 6)}...`)
|
||||
|
||||
db.pragma(`key = "x'${processedKey}'"`)
|
||||
|
||||
// 7. 验证连接
|
||||
// 如果参数错误,这里将抛出 "file is not a database" 或 "HMAC check failed"
|
||||
// 验证连接
|
||||
db.prepare('SELECT count(*) as count FROM sqlite_master').get()
|
||||
|
||||
// console.log(`✅ 连接成功!当前数据库 ${path.basename(targetPath)} 包含 ${row.count} 张表。`)
|
||||
return db
|
||||
} catch (error) {
|
||||
console.error(`❌ 连接失败:`, error)
|
||||
@@ -241,7 +221,7 @@ export class WechatDb {
|
||||
public getGroupMember(wxid: string): GroupMemberInfo | null {
|
||||
if (!this.correctUserId) return null
|
||||
|
||||
// 1. 检查缓存
|
||||
// 检查缓存
|
||||
if (this.groupMemberCache.has(wxid)) {
|
||||
return this.groupMemberCache.get(wxid) || null
|
||||
}
|
||||
@@ -261,7 +241,7 @@ export class WechatDb {
|
||||
|
||||
db.close()
|
||||
|
||||
// 2. 写入缓存
|
||||
// 写入缓存
|
||||
this.groupMemberCache.set(wxid, result || null)
|
||||
|
||||
return result || null
|
||||
|
||||
@@ -28,6 +28,7 @@ const systemPrompt = `你是一个中文的群聊总结的助手,你可以为
|
||||
2. 使用中文冒号
|
||||
3. 无需大标题
|
||||
4. 开始给出本群讨论风格的整体评价,例如活跃、太水、太黄、太暴力、话题不集中、无聊诸如此类
|
||||
5. 每个话题详细写出参与者
|
||||
|
||||
最后总结下今日最活跃的前五个发言者`
|
||||
|
||||
@@ -38,9 +39,11 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
onRefresh,
|
||||
onRefreshData
|
||||
}) => {
|
||||
const isGroupChat = contact?.type === 'group' || contact?.m_nsUsrName?.endsWith('@chatroom')
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const imageContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [generatedImage, setGeneratedImage] = useState<string | null>(null)
|
||||
const [showAvatar, setShowAvatar] = useState(false)
|
||||
|
||||
const [colWidths, setColWidths] = useState([150, 100, 180, 400])
|
||||
const [resizingColIndex, setResizingColIndex] = useState<number | null>(null)
|
||||
@@ -49,11 +52,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
|
||||
// AI Settings
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||
const [apiKey, setApiKey] = useState(() => localStorage.getItem('deepseek_api_key') || '')
|
||||
const [model, setModel] = useState('deepseek-chat')
|
||||
const [apiKey, setApiKey] = useState(() => localStorage.getItem('ai_api_key') || '')
|
||||
const [baseURL, setBaseURL] = useState(
|
||||
() => localStorage.getItem('ai_base_url') || 'https://api.deepseek.com'
|
||||
)
|
||||
const [model, setModel] = useState(() => localStorage.getItem('ai_model') || 'deepseek-chat')
|
||||
|
||||
const handleSaveSettings = (): void => {
|
||||
localStorage.setItem('deepseek_api_key', apiKey)
|
||||
localStorage.setItem('ai_api_key', apiKey)
|
||||
localStorage.setItem('ai_base_url', baseURL)
|
||||
localStorage.setItem('ai_model', model)
|
||||
setShowSettingsModal(false)
|
||||
AIChat()
|
||||
}
|
||||
@@ -113,11 +121,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
// 昨天
|
||||
const startOfYesterday = startOfDay - 86400000
|
||||
return parsed >= startOfYesterday && parsed < startOfDay
|
||||
} else {
|
||||
} else if (days === 7) {
|
||||
// 过去 7 天
|
||||
const startOf7DaysAgo = startOfDay - 7 * 86400000
|
||||
return parsed >= startOf7DaysAgo
|
||||
} else if (days === 30) {
|
||||
// 过去 30 天
|
||||
const startOf30DaysAgo = startOfDay - 30 * 86400000
|
||||
return parsed >= startOf30DaysAgo
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,7 +138,15 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...filtered.map((m) => {
|
||||
const content = m.content.replace(/"/g, '""').replace(/\n/g, ' ')
|
||||
let prefix = ''
|
||||
if (isGroupChat) {
|
||||
prefix = m.name ? `${m.name}: ` : ''
|
||||
} else {
|
||||
const name = m.from === 'user' ? contact?.m_nsNickName || '未知' : '我'
|
||||
prefix = `${name}: `
|
||||
}
|
||||
const fullContent = `${prefix}${m.content}`
|
||||
const content = fullContent.replace(/"/g, '""').replace(/\n/g, ' ')
|
||||
return `"${m.from}","${m.type}","${m.datetime}","${content}"`
|
||||
})
|
||||
].join('\n')
|
||||
@@ -148,16 +169,12 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
alert('当前没有消息可供总结')
|
||||
return
|
||||
}
|
||||
const filteredMessages = messages.filter((msg) => {
|
||||
delete msg.img
|
||||
// @ts-ignore 暂时去除
|
||||
delete msg.from
|
||||
// @ts-ignore 暂时去除
|
||||
delete msg.id
|
||||
// @ts-ignore 暂时去除
|
||||
delete msg.isSender
|
||||
return !'分享消息,图片,表情包,视频'.split(',').includes(msg.type)
|
||||
})
|
||||
const filteredMessages = messages
|
||||
.filter((msg) => !'分享消息,图片,表情包,视频'.split(',').includes(msg.type))
|
||||
.map((msg) => {
|
||||
const { img, id, isSender, ...rest } = msg
|
||||
return rest
|
||||
})
|
||||
const recentMessages = filteredMessages
|
||||
.map((msg) => {
|
||||
return `${msg.datetime} ${msg.from}: ${msg.content}`
|
||||
@@ -174,7 +191,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
{ apiKey, model }
|
||||
{ apiKey, model, baseURL }
|
||||
)
|
||||
|
||||
if (result.success && result.data) {
|
||||
@@ -292,14 +309,34 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
{msg.datetime}
|
||||
</td>
|
||||
<td style={{ wordBreak: 'break-all', display: 'flex', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginRight: 12 }}>
|
||||
<span>{msg.name}</span>
|
||||
{msg?.img && (
|
||||
<img style={{ width: '50px', height: '50px' }} src={msg?.img}></img>
|
||||
{showAvatar && msg?.img && (
|
||||
<img style={{ width: '40px', height: '40px' }} src={msg?.img}></img>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: !showAvatar ? 'flex' : 'block'
|
||||
}}
|
||||
>
|
||||
{(msg.name || contact.m_nsNickName) && (
|
||||
<div style={{ display: 'flex', fontSize: 18 }}>
|
||||
{isGroupChat ? msg.name : msg.from === 'user' ? contact.m_nsNickName : '我'}
|
||||
<span>{isGroupChat ? (msg.name ? ':' : '') : ':'} </span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 18,
|
||||
background: '#fff',
|
||||
margin: 4,
|
||||
padding: 4,
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
>
|
||||
{msg.content}
|
||||
</div>
|
||||
</div>
|
||||
{/* <span style={{ marginRight: '24px' }}>:</span> */}
|
||||
<div>{msg.content}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -325,6 +362,17 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="chat-toolbar">
|
||||
<label
|
||||
style={{ marginRight: '10px', display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showAvatar}
|
||||
onChange={(e) => setShowAvatar(e.target.checked)}
|
||||
style={{ marginRight: '5px' }}
|
||||
/>
|
||||
显示头像
|
||||
</label>
|
||||
<button className="toolbar-btn" onClick={onRefresh}>
|
||||
🔄 刷新聊天记录
|
||||
</button>
|
||||
@@ -343,6 +391,9 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
<button className="toolbar-btn" onClick={() => handleExport(7)}>
|
||||
📅 导出近7天
|
||||
</button>
|
||||
<button className="toolbar-btn" onClick={() => handleExport(30)}>
|
||||
📅 导出近30天
|
||||
</button>
|
||||
<button className="toolbar-btn" onClick={() => setShowSettingsModal(true)}>
|
||||
🤖 AI总结群聊
|
||||
</button>
|
||||
@@ -436,22 +487,37 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
<h3>AI 设置</h3>
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px' }}>模型:</label>
|
||||
<label style={{ display: 'block', marginBottom: '5px' }}>模型服务:</label>
|
||||
<select
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
style={{ width: '100%', padding: '8px' }}
|
||||
>
|
||||
<option value="deepseek-chat">DeepSeek Chat</option>
|
||||
<option value="gpt-4o">GPT-4o</option>
|
||||
<option value="gpt-4o-mini">GPT-4o Mini</option>
|
||||
<option value="gpt-4-turbo">GPT-4 Turbo</option>
|
||||
<option value="claude-3-5-sonnet-20240620">Claude 3.5 Sonnet</option>
|
||||
<option value="moonshot-v1-8k">Moonshot V1</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px' }}>Base URL:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={baseURL}
|
||||
onChange={(e) => setBaseURL(e.target.value)}
|
||||
placeholder="https://api.deepseek.com"
|
||||
style={{ width: '95%', padding: '8px' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '20px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px' }}>API Key:</label>
|
||||
<input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="Enter your DeepSeek API Key"
|
||||
placeholder="Enter your API Key"
|
||||
style={{ width: '95%', padding: '8px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }],
|
||||
"include": ["src", "vite-env.d.ts"]
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*"],
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*", "src/env.d.ts"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["electron-vite/node"]
|
||||
|
||||
Reference in New Issue
Block a user