Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f502049f4 | ||
|
|
c68ad4febb | ||
|
|
2ebcec9f59 | ||
|
|
7bc74a5b86 | ||
|
|
75152421d9 | ||
|
|
3326074076 | ||
|
|
362d82bdcc | ||
|
|
fcce241c82 | ||
|
|
693b06c126 | ||
|
|
c310c71576 | ||
|
|
bea95fc52f | ||
|
|
969cf8ea21 | ||
|
|
5b357f14e5 | ||
|
|
de5db4f805 | ||
|
|
1ccb5edda7 | ||
|
|
97b8749dd1 | ||
|
|
a6d7ecae81 | ||
|
|
938efb5aef | ||
|
|
9baf0f772e | ||
|
|
ff5de3625e | ||
|
|
f1cfdb29f8 | ||
|
|
26e48f07fd | ||
|
|
8bb5fb9811 | ||
|
|
d41667b599 |
@@ -1,5 +0,0 @@
|
||||
# Sponsor
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/user-attachments/assets/4665f07f-5ecc-4bd8-8727-ae00f35d6d98" alt="Buy Me a Coffee" width="280"/>
|
||||
</div>
|
||||
@@ -1,95 +0,0 @@
|
||||
# FAQ 文档
|
||||
本文档适用于:产品手册、官网页面、课程测验、现场 Q&A。
|
||||
|
||||
## 问题1:Cherry Studio 支持哪些操作系统?
|
||||
- **答案**:Cherry Studio 支持 Windows、Mac 和 Linux 操作系统。
|
||||
|
||||
## 问题2:Cherry Studio 的主要功能有哪些?
|
||||
- **答案**:Cherry Studio 的主要功能包括:
|
||||
1. 支持多个 LLM 提供商
|
||||
2. 允许创建多个助手
|
||||
3. 支持创建多个主题
|
||||
4. 允许在同一对话中使用多个模型来回答问题
|
||||
5. 支持拖放排序
|
||||
6. 代码高亮
|
||||
7. Mermaid 图表支持
|
||||
|
||||
## 问题3:Cherry Studio 的主要目录结构是怎样的?
|
||||
- **答案**:Cherry Studio 的主要目录结构如下:
|
||||
- `/src`: 主要源代码目录
|
||||
- `/build`: 构建相关文件
|
||||
- `/docs`: 文档目录
|
||||
- `/resources`: 资源文件目录
|
||||
- `/scripts`: 脚本文件目录
|
||||
|
||||
## 问题4:如何在 Windows 环境下 fork Cherry Studio 并修改部分功能?
|
||||
- **答案**:在 Windows 环境下 fork Cherry Studio 并修改部分功能的步骤如下:
|
||||
1. 在 GitHub 上 fork Cherry Studio 仓库
|
||||
2. 克隆 fork 的仓库到本地:`git clone https://github.com/your-username/cherry-studio.git`
|
||||
3. 进入项目目录:`cd cherry-studio`
|
||||
4. 安装依赖:`yarn install`
|
||||
5. 修改所需的功能代码
|
||||
6. 测试修改:`yarn dev`
|
||||
7. 提交修改:`git add .` 和 `git commit -m "描述你的修改"`
|
||||
8. 推送到你的 fork 仓库:`git push origin main`
|
||||
|
||||
## 问题5:Cherry Studio 使用了哪些主要技术栈?
|
||||
- **答案**:Cherry Studio 主要使用了以下技术栈:
|
||||
- TypeScript
|
||||
- SCSS
|
||||
- Electron
|
||||
- Vite
|
||||
- Sequelize
|
||||
|
||||
## 问题6:如何贡献代码到 Cherry Studio 项目?
|
||||
- **答案**:贡献代码到 Cherry Studio 项目的步骤如下:
|
||||
1. Fork 项目仓库
|
||||
2. 创建你的特性分支:`git checkout -b feature/AmazingFeature`
|
||||
3. 提交你的修改:`git commit -m 'Add some AmazingFeature'`
|
||||
4. 推送到分支:`git push origin feature/AmazingFeature`
|
||||
5. 打开一个 Pull Request
|
||||
|
||||
## 问题7:Cherry Studio 的 `/src` 目录主要包含哪些内容?
|
||||
- **答案**:Cherry Studio 的 `/src` 目录主要包含以下内容:
|
||||
- 主进程代码(Electron 主进程)
|
||||
- 渲染进程代码(用户界面)
|
||||
- 组件
|
||||
- 工具函数
|
||||
- 状态管理
|
||||
- 样式文件
|
||||
|
||||
## 问题8:如何在 Cherry Studio 中添加新的 LLM 提供商?
|
||||
- **答案**:要在 Cherry Studio 中添加新的 LLM 提供商,你需要:
|
||||
1. 在 `/src/services` 或类似目录下创建新的服务文件
|
||||
2. 实现与新 LLM 提供商 API 的集成
|
||||
3. 在用户界面中添加新提供商的选项
|
||||
4. 更新配置和状态管理以支持新提供商
|
||||
|
||||
## 问题9:Cherry Studio 的构建过程是怎样的?
|
||||
- **答案**:Cherry Studio 的构建过程主要包括:
|
||||
1. 使用 Vite 构建前端资源
|
||||
2. 使用 Electron Builder 打包桌面应用
|
||||
3. 根据不同平台(Windows、Mac、Linux)生成相应的安装包
|
||||
|
||||
## 问题10:如何在 Cherry Studio 中实现新的 UI 主题?
|
||||
- **答案**:在 Cherry Studio 中实现新的 UI 主题的步骤:
|
||||
1. 在 `/src/styles` 目录下创建新的主题 SCSS 文件
|
||||
2. 定义新主题的颜色变量和样式
|
||||
3. 在主样式文件中导入新主题
|
||||
4. 更新主题切换逻辑以包含新主题
|
||||
5. 在用户界面中添加新主题的选项
|
||||
|
||||
## 问题11:Cherry Studio 如何处理多语言支持?
|
||||
- **答案**:Cherry Studio 可能通过以下方式处理多语言支持:
|
||||
1. 使用 i18n 库进行国际化
|
||||
2. 在 `/src/locales` 或类似目录下存储不同语言的翻译文件
|
||||
3. 实现语言切换功能
|
||||
4. 在组件中使用翻译函数或组件来显示多语言文本
|
||||
|
||||
## 问题12:如何为 Cherry Studio 编写单元测试?
|
||||
- **答案**:为 Cherry Studio 编写单元测试的步骤:
|
||||
1. 在 `/tests` 目录下创建测试文件
|
||||
2. 使用测试框架(如 Jest)编写测试用例
|
||||
3. 模拟 Electron 环境和其他依赖
|
||||
4. 运行测试命令:`yarn test`
|
||||
5. 确保测试覆盖主要功能和组件
|
||||
@@ -65,11 +65,12 @@ afterSign: scripts/notarize.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
本次更新:
|
||||
支持更多的数学公式显示
|
||||
可以通过搜索快速切换模型
|
||||
增加 Bolt 小程序
|
||||
修复 Azure OpenAI 默认模型无法使用问题
|
||||
增加 Artifacts 网页预览功能
|
||||
内置助理新增网页生成助理
|
||||
DashScope 服务商修改为阿里云百炼
|
||||
修复粘贴长文本后不能自动清除的问题
|
||||
话题右键菜单增加删除消息功能
|
||||
修复选择模型弹窗滚动条消失问题
|
||||
近期更新:
|
||||
增加 WebDAV 备份功能 by @DrayChou
|
||||
增加话题历史记录
|
||||
增加 Azure OpenAI 服务商
|
||||
支持 PDF, DOC等办公文件格式
|
||||
支持图片的预览和下载
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "0.7.14",
|
||||
"version": "0.8.0",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -39,6 +39,7 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"officeparser": "^4.1.1",
|
||||
"unzipper": "^0.12.3",
|
||||
"webdav": "4.11.4"
|
||||
},
|
||||
|
||||
94
src/main/constant.ts
Normal file
94
src/main/constant.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
||||
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
|
||||
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||
export const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
'.mdx', // Markdown 文件
|
||||
'.html', // HTML 文件
|
||||
'.htm', // HTML 文件的另一种扩展名
|
||||
'.xml', // XML 文件
|
||||
'.json', // JSON 文件
|
||||
'.yaml', // YAML 文件
|
||||
'.yml', // YAML 文件的另一种扩展名
|
||||
'.csv', // 逗号分隔值文件
|
||||
'.tsv', // 制表符分隔值文件
|
||||
'.ini', // 配置文件
|
||||
'.log', // 日志文件
|
||||
'.rtf', // 富文本格式文件
|
||||
'.tex', // LaTeX 文件
|
||||
'.srt', // 字幕文件
|
||||
'.xhtml', // XHTML 文件
|
||||
'.nfo', // 信息文件(主要用于场景发布)
|
||||
'.conf', // 配置文件
|
||||
'.config', // 配置文件
|
||||
'.env', // 环境变量文件
|
||||
'.properties', // 配置属性文件
|
||||
'.latex', // LaTeX 文档文件
|
||||
'.rst', // reStructuredText 文件
|
||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||
'.ts', // TypeScript 文件
|
||||
'.jsp', // JavaServer Pages 文件
|
||||
'.aspx', // ASP.NET 文件
|
||||
'.bat', // Windows 批处理文件
|
||||
'.sh', // Unix/Linux Shell 脚本文件
|
||||
'.py', // Python 脚本文件
|
||||
'.rb', // Ruby 脚本文件
|
||||
'.pl', // Perl 脚本文件
|
||||
'.sql', // SQL 脚本文件
|
||||
'.css', // Cascading Style Sheets 文件
|
||||
'.less', // Less CSS 预处理器文件
|
||||
'.scss', // Sass CSS 预处理器文件
|
||||
'.sass', // Sass 文件
|
||||
'.styl', // Stylus CSS 预处理器文件
|
||||
'.coffee', // CoffeeScript 文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.asm', // Assembly 语言文件
|
||||
'.go', // Go 语言文件
|
||||
'.scala', // Scala 语言文件
|
||||
'.swift', // Swift 语言文件
|
||||
'.kt', // Kotlin 语言文件
|
||||
'.rs', // Rust 语言文件
|
||||
'.lua', // Lua 语言文件
|
||||
'.groovy', // Groovy 语言文件
|
||||
'.dart', // Dart 语言文件
|
||||
'.hs', // Haskell 语言文件
|
||||
'.clj', // Clojure 语言文件
|
||||
'.cljs', // ClojureScript 语言文件
|
||||
'.elm', // Elm 语言文件
|
||||
'.erl', // Erlang 语言文件
|
||||
'.ex', // Elixir 语言文件
|
||||
'.exs', // Elixir 脚本文件
|
||||
'.pug', // Pug (formerly Jade) 模板文件
|
||||
'.haml', // Haml 模板文件
|
||||
'.slim', // Slim 模板文件
|
||||
'.tpl', // 模板文件(通用)
|
||||
'.ejs', // Embedded JavaScript 模板文件
|
||||
'.hbs', // Handlebars 模板文件
|
||||
'.mustache', // Mustache 模板文件
|
||||
'.jade', // Jade 模板文件 (已重命名为 Pug)
|
||||
'.twig', // Twig 模板文件
|
||||
'.blade', // Blade 模板文件 (Laravel)
|
||||
'.vue', // Vue.js 单文件组件
|
||||
'.jsx', // React JSX 文件
|
||||
'.tsx', // React TSX 文件
|
||||
'.graphql', // GraphQL 查询语言文件
|
||||
'.gql', // GraphQL 查询语言文件
|
||||
'.proto', // Protocol Buffers 文件
|
||||
'.thrift', // Thrift 文件
|
||||
'.toml', // TOML 配置文件
|
||||
'.edn', // Clojure 数据表示文件
|
||||
'.cake', // CakePHP 配置文件
|
||||
'.ctp', // CakePHP 视图文件
|
||||
'.cfm', // ColdFusion 标记语言文件
|
||||
'.cfc', // ColdFusion 组件文件
|
||||
'.m', // Objective-C 源文件
|
||||
'.mm', // Objective-C++ 源文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.groovy', // Gradle 构建文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.kts' // Kotlin Script 文件
|
||||
]
|
||||
@@ -4,6 +4,7 @@ import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config'
|
||||
import AppUpdater from './services/AppUpdater'
|
||||
import BackupManager from './services/BackupManager'
|
||||
import FileManager from './services/FileManager'
|
||||
import { compress, decompress } from './utils/zip'
|
||||
import { createMinappWindow } from './window'
|
||||
|
||||
const fileManager = new FileManager()
|
||||
@@ -29,6 +30,8 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
ipcMain.handle('reload', () => mainWindow.reload())
|
||||
|
||||
ipcMain.handle('zip:compress', (_, text: string) => compress(text))
|
||||
ipcMain.handle('zip:decompress', (_, text: Buffer) => decompress(text))
|
||||
ipcMain.handle('backup:backup', backupManager.backup)
|
||||
ipcMain.handle('backup:restore', backupManager.restore)
|
||||
ipcMain.handle('backup:backupToWebdav', backupManager.backupToWebdav)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { documentExts } from '@main/constant'
|
||||
import { getFileType } from '@main/utils/file'
|
||||
import { FileType } from '@types'
|
||||
import * as crypto from 'crypto'
|
||||
@@ -13,11 +14,14 @@ import logger from 'electron-log'
|
||||
import * as fs from 'fs'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { readFile } from 'fs/promises'
|
||||
import officeParser from 'officeparser'
|
||||
import * as path from 'path'
|
||||
import { chdir } from 'process'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
class FileManager {
|
||||
private storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
||||
private tempDir = path.join(app.getPath('temp'), 'CherryStudio')
|
||||
|
||||
constructor() {
|
||||
this.initStorageDir()
|
||||
@@ -27,6 +31,9 @@ class FileManager {
|
||||
if (!fs.existsSync(this.storageDir)) {
|
||||
fs.mkdirSync(this.storageDir, { recursive: true })
|
||||
}
|
||||
if (!fs.existsSync(this.tempDir)) {
|
||||
fs.mkdirSync(this.tempDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
private getFileHash = async (filePath: string): Promise<string> => {
|
||||
@@ -173,15 +180,29 @@ class FileManager {
|
||||
|
||||
public readFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<string> => {
|
||||
const filePath = path.join(this.storageDir, id)
|
||||
|
||||
if (documentExts.includes(path.extname(filePath))) {
|
||||
const originalCwd = process.cwd()
|
||||
try {
|
||||
chdir(this.tempDir)
|
||||
const data = await officeParser.parseOfficeAsync(filePath)
|
||||
chdir(originalCwd)
|
||||
return data
|
||||
} catch (error) {
|
||||
chdir(originalCwd)
|
||||
logger.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return fs.readFileSync(filePath, 'utf8')
|
||||
}
|
||||
|
||||
public createTempFile = async (_: Electron.IpcMainInvokeEvent, fileName: string): Promise<string> => {
|
||||
const tempDir = path.join(app.getPath('temp'), 'CherryStudio')
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true })
|
||||
if (!fs.existsSync(this.tempDir)) {
|
||||
fs.mkdirSync(this.tempDir, { recursive: true })
|
||||
}
|
||||
const tempFilePath = path.join(tempDir, `temp_file_${uuidv4()}_${fileName}`)
|
||||
const tempFilePath = path.join(this.tempDir, `temp_file_${uuidv4()}_${fileName}`)
|
||||
return tempFilePath
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +1,8 @@
|
||||
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@main/constant'
|
||||
|
||||
import { FileTypes } from '../../renderer/src/types'
|
||||
|
||||
export function getFileType(ext: string): FileTypes {
|
||||
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
||||
const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
|
||||
const documentExts = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
|
||||
const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
'.mdx', // Markdown 文件
|
||||
'.html', // HTML 文件
|
||||
'.htm', // HTML 文件的另一种扩展名
|
||||
'.xml', // XML 文件
|
||||
'.json', // JSON 文件
|
||||
'.yaml', // YAML 文件
|
||||
'.yml', // YAML 文件的另一种扩展名
|
||||
'.csv', // 逗号分隔值文件
|
||||
'.tsv', // 制表符分隔值文件
|
||||
'.ini', // 配置文件
|
||||
'.log', // 日志文件
|
||||
'.rtf', // 富文本格式文件
|
||||
'.tex', // LaTeX 文件
|
||||
'.srt', // 字幕文件
|
||||
'.xhtml', // XHTML 文件
|
||||
'.nfo', // 信息文件(主要用于场景发布)
|
||||
'.conf', // 配置文件
|
||||
'.config', // 配置文件
|
||||
'.env', // 环境变量文件
|
||||
'.properties', // 配置属性文件
|
||||
'.latex', // LaTeX 文档文件
|
||||
'.rst', // reStructuredText 文件
|
||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||
'.ts', // TypeScript 文件
|
||||
'.jsp', // JavaServer Pages 文件
|
||||
'.aspx', // ASP.NET 文件
|
||||
'.bat', // Windows 批处理文件
|
||||
'.sh', // Unix/Linux Shell 脚本文件
|
||||
'.py', // Python 脚本文件
|
||||
'.rb', // Ruby 脚本文件
|
||||
'.pl', // Perl 脚本文件
|
||||
'.sql', // SQL 脚本文件
|
||||
'.css', // Cascading Style Sheets 文件
|
||||
'.less', // Less CSS 预处理器文件
|
||||
'.scss', // Sass CSS 预处理器文件
|
||||
'.sass', // Sass 文件
|
||||
'.styl', // Stylus CSS 预处理器文件
|
||||
'.coffee', // CoffeeScript 文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.asm', // Assembly 语言文件
|
||||
'.go', // Go 语言文件
|
||||
'.scala', // Scala 语言文件
|
||||
'.swift', // Swift 语言文件
|
||||
'.kt', // Kotlin 语言文件
|
||||
'.rs', // Rust 语言文件
|
||||
'.lua', // Lua 语言文件
|
||||
'.groovy', // Groovy 语言文件
|
||||
'.dart', // Dart 语言文件
|
||||
'.hs', // Haskell 语言文件
|
||||
'.clj', // Clojure 语言文件
|
||||
'.cljs', // ClojureScript 语言文件
|
||||
'.elm', // Elm 语言文件
|
||||
'.erl', // Erlang 语言文件
|
||||
'.ex', // Elixir 语言文件
|
||||
'.exs', // Elixir 脚本文件
|
||||
'.pug', // Pug (formerly Jade) 模板文件
|
||||
'.haml', // Haml 模板文件
|
||||
'.slim', // Slim 模板文件
|
||||
'.tpl', // 模板文件(通用)
|
||||
'.ejs', // Embedded JavaScript 模板文件
|
||||
'.hbs', // Handlebars 模板文件
|
||||
'.mustache', // Mustache 模板文件
|
||||
'.jade', // Jade 模板文件 (已重命名为 Pug)
|
||||
'.twig', // Twig 模板文件
|
||||
'.blade', // Blade 模板文件 (Laravel)
|
||||
'.vue', // Vue.js 单文件组件
|
||||
'.jsx', // React JSX 文件
|
||||
'.tsx', // React TSX 文件
|
||||
'.graphql', // GraphQL 查询语言文件
|
||||
'.gql', // GraphQL 查询语言文件
|
||||
'.proto', // Protocol Buffers 文件
|
||||
'.thrift', // Thrift 文件
|
||||
'.toml', // TOML 配置文件
|
||||
'.edn', // Clojure 数据表示文件
|
||||
'.cake', // CakePHP 配置文件
|
||||
'.ctp', // CakePHP 视图文件
|
||||
'.cfm', // ColdFusion 标记语言文件
|
||||
'.cfc', // ColdFusion 组件文件
|
||||
'.m', // Objective-C 源文件
|
||||
'.mm', // Objective-C++ 源文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.groovy', // Gradle 构建文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.kts' // Kotlin Script 文件
|
||||
]
|
||||
|
||||
ext = ext.toLowerCase()
|
||||
if (imageExts.includes(ext)) return FileTypes.IMAGE
|
||||
if (videoExts.includes(ext)) return FileTypes.VIDEO
|
||||
|
||||
39
src/main/utils/zip.ts
Normal file
39
src/main/utils/zip.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import util from 'node:util'
|
||||
import zlib from 'node:zlib'
|
||||
|
||||
import logger from 'electron-log'
|
||||
|
||||
// 将 zlib 的 gzip 和 gunzip 方法转换为 Promise 版本
|
||||
const gzipPromise = util.promisify(zlib.gzip)
|
||||
const gunzipPromise = util.promisify(zlib.gunzip)
|
||||
|
||||
/**
|
||||
* 压缩字符串
|
||||
* @param {string} string - 要压缩的 JSON 字符串
|
||||
* @returns {Promise<Buffer>} 压缩后的 Buffer
|
||||
*/
|
||||
export async function compress(str) {
|
||||
try {
|
||||
const buffer = Buffer.from(str, 'utf-8')
|
||||
const compressedBuffer = await gzipPromise(buffer)
|
||||
return compressedBuffer
|
||||
} catch (error) {
|
||||
logger.error('Compression failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩 Buffer 到 JSON 字符串
|
||||
* @param {Buffer} compressedBuffer - 压缩的 Buffer
|
||||
* @returns {Promise<string>} 解压缩后的 JSON 字符串
|
||||
*/
|
||||
export async function decompress(compressedBuffer) {
|
||||
try {
|
||||
const buffer = await gunzipPromise(compressedBuffer)
|
||||
return buffer.toString('utf-8')
|
||||
} catch (error) {
|
||||
logger.error('Decompression failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ export function createMainWindow() {
|
||||
const theme = appConfig.get('theme') || 'light'
|
||||
|
||||
// Create the browser window.
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
@@ -25,11 +27,12 @@ export function createMainWindow() {
|
||||
minHeight: 600,
|
||||
show: true,
|
||||
autoHideMenuBar: true,
|
||||
transparent: process.platform === 'darwin',
|
||||
transparent: isMac,
|
||||
vibrancy: 'fullscreen-ui',
|
||||
visualEffectState: 'active',
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
||||
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
|
||||
trafficLightPosition: { x: 8, y: 12 },
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
|
||||
@@ -11,6 +11,8 @@ const api = {
|
||||
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme),
|
||||
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
|
||||
reload: () => ipcRenderer.invoke('reload'),
|
||||
compress: (text: string) => ipcRenderer.invoke('zip:compress', text),
|
||||
decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text),
|
||||
backup: {
|
||||
backup: (fileName: string, data: string, destinationPath?: string) =>
|
||||
ipcRenderer.invoke('backup:backup', fileName, data, destinationPath),
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; connect-src *; script-src 'self' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: *; frame-src * file:" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
#spinner {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
#spinner img {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="spinner">
|
||||
<img src="/src/assets/images/logo/cherry-text.svg" />
|
||||
</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; connect-src *; script-src 'self' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: *; frame-src * file:" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#spinner {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#spinner img {
|
||||
width: 100px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="spinner">
|
||||
<img src="/src/assets/images/logo.png" />
|
||||
</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
src/renderer/src/assets/images/providers/bailian.png
Normal file
BIN
src/renderer/src/assets/images/providers/bailian.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -55,6 +55,8 @@
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
white-space: pre-wrap;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* 全局初始化滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
width: 4px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background: var(--color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
size="middle"
|
||||
/>
|
||||
</HStack>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} />
|
||||
<Container>
|
||||
{agents.map((agent) => (
|
||||
<AgentItem
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId } from '@renderer/services/model'
|
||||
import { Model } from '@renderer/types'
|
||||
import { Avatar, Divider, Empty, Input, InputRef, Menu, MenuProps, Modal } from 'antd'
|
||||
import { first, reverse, sortBy, upperFirst } from 'lodash'
|
||||
import { first, reverse, sortBy } from 'lodash'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -42,7 +42,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
key: getModelUniqId(m),
|
||||
label: (
|
||||
<ModelItem>
|
||||
{upperFirst(m?.name)} {isVisionModel(m) && <VisionIcon />}
|
||||
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
||||
</ModelItem>
|
||||
),
|
||||
icon: (
|
||||
@@ -100,7 +100,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
size="middle"
|
||||
/>
|
||||
</HStack>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} />
|
||||
<Container>
|
||||
{filteredItems.length > 0 ? (
|
||||
<StyledMenu
|
||||
@@ -123,9 +123,6 @@ const Container = styled.div`
|
||||
height: 50vh;
|
||||
margin-top: 10px;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMenu = styled(Menu)`
|
||||
@@ -133,7 +130,6 @@ const StyledMenu = styled(Menu)`
|
||||
padding: 5px;
|
||||
margin-top: -10px;
|
||||
max-height: calc(60vh - 50px);
|
||||
overflow-y: auto;
|
||||
|
||||
.ant-menu-item-group-title {
|
||||
padding: 5px 10px 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -9,6 +9,7 @@ export const isWindows = platform === 'win32' || platform === 'win64'
|
||||
export const isLinux = platform === 'linux'
|
||||
|
||||
export const imageExts = ['.jpg', '.png', '.jpeg']
|
||||
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||
export const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
|
||||
@@ -695,7 +695,7 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
group: 'Baichuan3'
|
||||
}
|
||||
],
|
||||
dashscope: [
|
||||
bailian: [
|
||||
{
|
||||
id: 'qwen-turbo',
|
||||
provider: 'dashscope',
|
||||
|
||||
@@ -3,8 +3,8 @@ import AzureProviderLogo from '@renderer/assets/images/models/microsoft.png'
|
||||
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
|
||||
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png'
|
||||
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
|
||||
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
|
||||
import BytedanceProviderLogo from '@renderer/assets/images/providers/bytedance.png'
|
||||
import DashScopeProviderLogo from '@renderer/assets/images/providers/dashscope.png'
|
||||
import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png'
|
||||
import FireworksProviderLogo from '@renderer/assets/images/providers/fireworks.png'
|
||||
import GithubProviderLogo from '@renderer/assets/images/providers/github.png'
|
||||
@@ -47,7 +47,7 @@ export function getProviderLogo(providerId: string) {
|
||||
case 'baichuan':
|
||||
return BaichuanProviderLogo
|
||||
case 'dashscope':
|
||||
return DashScopeProviderLogo
|
||||
return BailianProviderLogo
|
||||
case 'anthropic':
|
||||
return AnthropicProviderLogo
|
||||
case 'aihubmix':
|
||||
@@ -208,10 +208,10 @@ export const PROVIDER_CONFIG = {
|
||||
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://dashscope.aliyun.com/',
|
||||
apiKey: 'https://help.aliyun.com/zh/dashscope/developer-reference/acquisition-and-configuration-of-api-key',
|
||||
docs: 'https://help.aliyun.com/zh/dashscope/',
|
||||
models: 'https://dashscope.console.aliyun.com/model'
|
||||
official: 'https://www.aliyun.com/product/bailian',
|
||||
apiKey: 'https://bailian.console.aliyun.com/?apiKey=1#/api-key',
|
||||
docs: 'https://help.aliyun.com/zh/model-studio/getting-started/',
|
||||
models: 'https://bailian.console.aliyun.com/model-market#/model-market'
|
||||
}
|
||||
},
|
||||
stepfun: {
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"topics.auto_rename": "Auto Rename",
|
||||
"topics.edit.title": "Edit Name",
|
||||
"topics.edit.placeholder": "Enter new name",
|
||||
"topics.clear.title": "Clear Messages",
|
||||
"topics.delete.all.title": "Delete all topics",
|
||||
"topics.delete.all.content": "Are you sure you want to delete all topics?",
|
||||
"topics.move_to": "Move to",
|
||||
@@ -90,7 +91,7 @@
|
||||
"input.send": "Send",
|
||||
"input.pause": "Pause",
|
||||
"input.settings": "Settings",
|
||||
"input.upload": "Upload image or text file",
|
||||
"input.upload": "Upload image or document file",
|
||||
"input.context_count.tip": "Context Count",
|
||||
"input.estimated_tokens.tip": "Estimated tokens",
|
||||
"settings.temperature": "Temperature",
|
||||
@@ -106,7 +107,9 @@
|
||||
"add.assistant.title": "Add Assistant",
|
||||
"message.new.context": "New Context",
|
||||
"message.new.branch": "New Branch",
|
||||
"assistant.search.placeholder": "Search"
|
||||
"assistant.search.placeholder": "Search",
|
||||
"artifacts.button.preview": "Preview",
|
||||
"artifacts.button.download": "Download"
|
||||
},
|
||||
"assistants": {
|
||||
"title": "Assistants",
|
||||
@@ -169,7 +172,7 @@
|
||||
"groq": "Groq",
|
||||
"ollama": "Ollama",
|
||||
"baichuan": "Baichuan",
|
||||
"dashscope": "DashScope",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
"anthropic": "Anthropic",
|
||||
"aihubmix": "AiHubMix",
|
||||
"stepfun": "StepFun",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"topics.auto_rename": "生成话题名",
|
||||
"topics.edit.title": "编辑话题名",
|
||||
"topics.edit.placeholder": "输入新名称",
|
||||
"topics.clear.title": "清空消息",
|
||||
"topics.delete.all.title": "删除所有话题",
|
||||
"topics.delete.all.content": "确定要删除所有话题吗?",
|
||||
"topics.move_to": "移动到",
|
||||
@@ -84,13 +85,13 @@
|
||||
"input.new.context": "清除上下文",
|
||||
"input.expand": "展开",
|
||||
"input.collapse": "收起",
|
||||
"input.clear.title": "清除消息?",
|
||||
"input.clear.title": "清空消息",
|
||||
"input.clear.content": "确定要清除当前会话所有消息吗?",
|
||||
"input.placeholder": "在这里输入消息...",
|
||||
"input.send": "发送",
|
||||
"input.pause": "暂停",
|
||||
"input.settings": "设置",
|
||||
"input.upload": "上传图片或纯文本文件",
|
||||
"input.upload": "上传图片或文档",
|
||||
"input.context_count.tip": "上下文数",
|
||||
"input.estimated_tokens.tip": "预估 token 数",
|
||||
"settings.temperature": "模型温度",
|
||||
@@ -106,7 +107,9 @@
|
||||
"add.assistant.title": "添加助手",
|
||||
"message.new.context": "清除上下文",
|
||||
"message.new.branch": "新分支",
|
||||
"assistant.search.placeholder": "搜索"
|
||||
"assistant.search.placeholder": "搜索",
|
||||
"artifacts.button.preview": "预览",
|
||||
"artifacts.button.download": "下载"
|
||||
},
|
||||
"assistants": {
|
||||
"title": "助手",
|
||||
@@ -169,7 +172,7 @@
|
||||
"groq": "Groq",
|
||||
"ollama": "Ollama",
|
||||
"baichuan": "百川",
|
||||
"dashscope": "阿里云灵积",
|
||||
"dashscope": "阿里云百炼",
|
||||
"anthropic": "Anthropic",
|
||||
"aihubmix": "AiHubMix",
|
||||
"stepfun": "阶跃星辰",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"topics.auto_rename": "自動重新命名",
|
||||
"topics.edit.title": "編輯名稱",
|
||||
"topics.edit.placeholder": "輸入新名稱",
|
||||
"topics.clear.title": "清空消息",
|
||||
"topics.delete.all.title": "刪除所有話題",
|
||||
"topics.delete.all.content": "確定要刪除所有話題嗎?",
|
||||
"topics.move_to": "移動到",
|
||||
@@ -90,7 +91,7 @@
|
||||
"input.send": "發送",
|
||||
"input.pause": "暫停",
|
||||
"input.settings": "設定",
|
||||
"input.upload": "上傳圖片或文字檔",
|
||||
"input.upload": "上傳圖片或文檔",
|
||||
"input.context_count.tip": "上下文數量",
|
||||
"input.estimated_tokens.tip": "預估 Token 數",
|
||||
"settings.temperature": "溫度",
|
||||
@@ -106,7 +107,9 @@
|
||||
"add.assistant.title": "添加助手",
|
||||
"message.new.context": "新上下文",
|
||||
"message.new.branch": "新分支",
|
||||
"assistant.search.placeholder": "搜尋"
|
||||
"assistant.search.placeholder": "搜尋",
|
||||
"artifacts.button.preview": "預覽",
|
||||
"artifacts.button.download": "下載"
|
||||
},
|
||||
"assistants": {
|
||||
"title": "助手",
|
||||
@@ -164,12 +167,12 @@
|
||||
"moonshot": "月之暗面",
|
||||
"silicon": "SiliconFlow",
|
||||
"openrouter": "OpenRouter",
|
||||
"yi": "零一万物",
|
||||
"zhipu": "智谱AI",
|
||||
"yi": "零一萬物",
|
||||
"zhipu": "智譜AI",
|
||||
"groq": "Groq",
|
||||
"ollama": "Ollama",
|
||||
"baichuan": "百川",
|
||||
"dashscope": "DashScope",
|
||||
"dashscope": "阿里雲百鍊",
|
||||
"anthropic": "Anthropic",
|
||||
"aihubmix": "AiHubMix",
|
||||
"stepfun": "StepFun",
|
||||
|
||||
@@ -61,22 +61,24 @@ const AppsPage: FC = () => {
|
||||
{agents.length > 0 && <ManageIcon onClick={ManageAgentsPopup.show} />}
|
||||
</HStack>
|
||||
<UserAgents onAdd={onAddAgentConfirm} />
|
||||
{Object.keys(agentGroups).map((group) => (
|
||||
<div key={group}>
|
||||
<Title level={4} key={group} style={{ marginBottom: 16 }}>
|
||||
{group}
|
||||
</Title>
|
||||
<Row gutter={16}>
|
||||
{agentGroups[group].map((agent, index) => {
|
||||
return (
|
||||
<Col span={8} key={group + index}>
|
||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
{Object.keys(agentGroups)
|
||||
.reverse()
|
||||
.map((group) => (
|
||||
<div key={group}>
|
||||
<Title level={4} key={group} style={{ marginBottom: 16 }}>
|
||||
{group}
|
||||
</Title>
|
||||
<Row gutter={16}>
|
||||
{agentGroups[group].map((agent, index) => {
|
||||
return (
|
||||
<Col span={8} key={group + index}>
|
||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ minHeight: 20 }} />
|
||||
</AssistantsContainer>
|
||||
</ContentContainer>
|
||||
|
||||
@@ -8,7 +8,7 @@ import styled from 'styled-components'
|
||||
|
||||
import Inputbar from './Inputbar/Inputbar'
|
||||
import Messages from './Messages/Messages'
|
||||
import RightSidebar from './Tabs'
|
||||
import Tabs from './Tabs'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
@@ -29,7 +29,7 @@ const Chat: FC<Props> = (props) => {
|
||||
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
|
||||
</Main>
|
||||
{topicPosition === 'right' && showTopics && (
|
||||
<RightSidebar
|
||||
<Tabs
|
||||
activeAssistant={assistant}
|
||||
activeTopic={props.activeTopic}
|
||||
setActiveAssistant={props.setActiveAssistant}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PaperClipOutlined } from '@ant-design/icons'
|
||||
import { imageExts, textExts } from '@renderer/config/constant'
|
||||
import { documentExts, imageExts, textExts } from '@renderer/config/constant'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import { FileType, Model } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
@@ -15,7 +15,9 @@ interface Props {
|
||||
|
||||
const AttachmentButton: FC<Props> = ({ model, files, setFiles, ToolbarButton }) => {
|
||||
const { t } = useTranslation()
|
||||
const extensions = isVisionModel(model) ? [...imageExts, ...textExts] : [...textExts]
|
||||
const extensions = isVisionModel(model)
|
||||
? [...imageExts, ...documentExts, ...textExts]
|
||||
: [...documentExts, ...textExts]
|
||||
|
||||
const onSelectFile = async () => {
|
||||
if (files.length > 0) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PauseCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { imageExts, textExts } from '@renderer/config/constant'
|
||||
import { documentExts, imageExts, textExts } from '@renderer/config/constant'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
@@ -16,7 +16,7 @@ import { useRuntime, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import FileManager from '@renderer/services/file'
|
||||
import { estimateTextTokens } from '@renderer/services/tokens'
|
||||
import { estimateTextTokens as estimateTxtTokens } from '@renderer/services/tokens'
|
||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setGenerating, setSearching } from '@renderer/store/runtime'
|
||||
import { Assistant, FileType, Message, Topic } from '@renderer/types'
|
||||
@@ -46,7 +46,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const [text, setText] = useState(_text)
|
||||
const [inputFocus, setInputFocus] = useState(false)
|
||||
const { addTopic, model } = useAssistant(assistant.id)
|
||||
const { sendMessageShortcut, fontSize, pasteLongTextAsFile } = useSettings()
|
||||
const { sendMessageShortcut, fontSize, pasteLongTextAsFile, showInputEstimatedTokens } = useSettings()
|
||||
const [expended, setExpend] = useState(false)
|
||||
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||
const [contextCount, setContextCount] = useState(0)
|
||||
@@ -60,8 +60,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
||||
const supportExts = useMemo(() => [...textExts, ...(isVision ? imageExts : [])], [isVision])
|
||||
const inputTokenCount = useMemo(() => estimateTextTokens(text), [text])
|
||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
||||
|
||||
const estimateTextTokens = useCallback(debounce(estimateTxtTokens, 1000), [])
|
||||
const inputTokenCount = useMemo(
|
||||
() => (showInputEstimatedTokens ? estimateTextTokens(text) || 0 : 0),
|
||||
[estimateTextTokens, showInputEstimatedTokens, text]
|
||||
)
|
||||
|
||||
_text = text
|
||||
_files = files
|
||||
@@ -207,14 +212,14 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
await window.api.file.write(tempFilePath, pasteText)
|
||||
const selectedFile = await window.api.file.get(tempFilePath)
|
||||
selectedFile && setFiles((prevFiles) => [...prevFiles, selectedFile])
|
||||
setText((prevText) => prevText.replace(pasteText, ''))
|
||||
setText(text)
|
||||
setTimeout(() => resizeTextArea(), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
[pasteLongTextAsFile, supportExts]
|
||||
[pasteLongTextAsFile, supportExts, text]
|
||||
)
|
||||
|
||||
// Command or Ctrl + N create new topic
|
||||
@@ -243,10 +248,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
EventEmitter.on(EVENT_NAMES.ESTIMATED_TOKEN_COUNT, ({ tokensCount, contextCount }) => {
|
||||
_setEstimateTokenCount(tokensCount)
|
||||
setContextCount(contextCount)
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.ADD_NEW_TOPIC, () => {
|
||||
addNewTopic()
|
||||
})
|
||||
]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [])
|
||||
}, [addNewTopic])
|
||||
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus()
|
||||
|
||||
51
src/renderer/src/pages/home/Markdown/Artifacts.tsx
Normal file
51
src/renderer/src/pages/home/Markdown/Artifacts.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import MinApp from '@renderer/components/MinApp'
|
||||
import { AppLogo } from '@renderer/config/env'
|
||||
import { extractTitle } from '@renderer/utils/formula'
|
||||
import { Button } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
html: string
|
||||
}
|
||||
|
||||
const Artifacts: FC<Props> = ({ html }) => {
|
||||
const { t } = useTranslation()
|
||||
const title = extractTitle(html) || 'Artifacts' + ' ' + t('chat.artifacts.button.preview')
|
||||
|
||||
const onPreview = async () => {
|
||||
const path = await window.api.file.create('artifacts-preview.html')
|
||||
await window.api.file.write(path, html)
|
||||
|
||||
MinApp.start({
|
||||
name: title,
|
||||
logo: AppLogo,
|
||||
url: `file://${path}`
|
||||
})
|
||||
}
|
||||
|
||||
const onDownload = () => {
|
||||
window.api.file.save(`${title}.html`, html)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Button type="primary" size="middle" onClick={onPreview}>
|
||||
{t('chat.artifacts.button.preview')}
|
||||
</Button>
|
||||
<Button size="middle" onClick={onDownload}>
|
||||
{t('chat.artifacts.button.download')}
|
||||
</Button>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
export default Artifacts
|
||||
@@ -9,6 +9,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import { atomDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Artifacts from './Artifacts'
|
||||
import Mermaid from './Mermaid'
|
||||
|
||||
interface CodeBlockProps {
|
||||
@@ -21,8 +22,9 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
const showFooterCopyButton = children && children.length > 500
|
||||
const { theme } = useTheme()
|
||||
const language = match?.[1]
|
||||
|
||||
if (match && match[1] === 'mermaid') {
|
||||
if (language === 'mermaid') {
|
||||
initMermaid(theme)
|
||||
return <Mermaid chart={children} />
|
||||
}
|
||||
@@ -50,6 +52,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
<CopyButton text={children} style={{ marginTop: -40, marginRight: 10 }} />
|
||||
</CodeFooter>
|
||||
)}
|
||||
{language === 'html' && children?.includes('</html>') && <Artifacts html={children} />}
|
||||
</div>
|
||||
) : (
|
||||
<code className={className}>{children}</code>
|
||||
|
||||
62
src/renderer/src/pages/home/Markdown/ImagePreview.tsx
Normal file
62
src/renderer/src/pages/home/Markdown/ImagePreview.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
DownloadOutlined,
|
||||
RotateLeftOutlined,
|
||||
RotateRightOutlined,
|
||||
SwapOutlined,
|
||||
UndoOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { download } from '@renderer/utils/download'
|
||||
import { Image, Space } from 'antd'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface ImagePreviewProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
src: string
|
||||
}
|
||||
|
||||
const ImagePreview: React.FC<ImagePreviewProps> = ({ src }) => {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
preview={{
|
||||
toolbarRender: (
|
||||
_,
|
||||
{
|
||||
transform: { scale },
|
||||
actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn, onReset }
|
||||
}
|
||||
) => (
|
||||
<ToobarWrapper size={12} className="toolbar-wrapper">
|
||||
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||
<SwapOutlined onClick={onFlipX} />
|
||||
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||
<RotateRightOutlined onClick={onRotateRight} />
|
||||
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||
<UndoOutlined onClick={onReset} />
|
||||
<DownloadOutlined onClick={() => download(src)} />
|
||||
</ToobarWrapper>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ToobarWrapper = styled(Space)`
|
||||
padding: 0px 24px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 100px;
|
||||
.anticon {
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.anticon:hover {
|
||||
opacity: 0.3;
|
||||
}
|
||||
`
|
||||
|
||||
export default ImagePreview
|
||||
@@ -14,20 +14,13 @@ import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
import CodeBlock from './CodeBlock'
|
||||
import ImagePreview from './ImagePreview'
|
||||
import Link from './Link'
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
}
|
||||
|
||||
const rehypePlugins = [rehypeRaw, rehypeMathjax]
|
||||
const remarkPlugins = [remarkMath, remarkGfm]
|
||||
|
||||
const components = {
|
||||
code: CodeBlock,
|
||||
a: Link
|
||||
}
|
||||
|
||||
const Markdown: FC<Props> = ({ message }) => {
|
||||
const { t } = useTranslation()
|
||||
const { renderInputMessageAsMarkdown } = useSettings()
|
||||
@@ -39,6 +32,11 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
return escapeBrackets(content)
|
||||
}, [message.content, message.status, t])
|
||||
|
||||
const rehypePlugins = useMemo(() => {
|
||||
const hasUnsafeElements = /<(input|textarea|select)/i.test(messageContent)
|
||||
return hasUnsafeElements ? [rehypeMathjax] : [rehypeRaw, rehypeMathjax]
|
||||
}, [messageContent])
|
||||
|
||||
if (message.role === 'user' && !renderInputMessageAsMarkdown) {
|
||||
return <p style={{ marginBottom: 5, whiteSpace: 'pre-wrap' }}>{messageContent}</p>
|
||||
}
|
||||
@@ -47,8 +45,14 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
<ReactMarkdown
|
||||
className="markdown"
|
||||
rehypePlugins={rehypePlugins}
|
||||
remarkPlugins={remarkPlugins}
|
||||
components={components as Partial<Components>}
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
components={
|
||||
{
|
||||
a: Link,
|
||||
code: CodeBlock,
|
||||
img: ImagePreview
|
||||
} as Partial<Components>
|
||||
}
|
||||
remarkRehypeOptions={{
|
||||
footnoteLabel: t('common.footnotes'),
|
||||
footnoteLabelTagName: 'h4',
|
||||
|
||||
@@ -42,15 +42,17 @@ const MessageItem: FC<Props> = ({ message, index, lastMessage, showMenu = true,
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribes = [
|
||||
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, () => {
|
||||
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, (highlight: boolean = true) => {
|
||||
if (messageRef.current) {
|
||||
messageRef.current.scrollIntoView({ behavior: 'smooth' })
|
||||
setTimeout(() => {
|
||||
messageRef.current?.classList.add('message-highlight')
|
||||
if (highlight) {
|
||||
setTimeout(() => {
|
||||
messageRef.current?.classList.remove('message-highlight')
|
||||
}, 2500)
|
||||
}, 500)
|
||||
messageRef.current?.classList.add('message-highlight')
|
||||
setTimeout(() => {
|
||||
messageRef.current?.classList.remove('message-highlight')
|
||||
}, 2500)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Assistant, Message, Model } from '@renderer/types'
|
||||
import { firstLetter, removeLeadingEmoji } from '@renderer/utils'
|
||||
import { Avatar } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { upperFirst } from 'lodash'
|
||||
import { FC, useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -36,7 +35,7 @@ const MessageHeader: FC<Props> = ({ assistant, model, message }) => {
|
||||
|
||||
const getUserName = useCallback(() => {
|
||||
if (isLocalAi && message.role !== 'user') return APP_NAME
|
||||
if (message.role === 'assistant') return upperFirst(model?.name || model?.id)
|
||||
if (message.role === 'assistant') return model?.name || model?.id || ''
|
||||
return userName || t('common.you')
|
||||
}, [message.role, model?.id, model?.name, t, userName])
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { useRuntime } from '@renderer/hooks/useStore'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { Message } from '@renderer/types'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const MessgeTokens: React.FC<{ message: Message }> = ({ message }) => {
|
||||
const { generating } = useRuntime()
|
||||
|
||||
const locateMessage = () => {
|
||||
EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, false)
|
||||
}
|
||||
|
||||
if (!message.usage) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (message.role === 'user') {
|
||||
return <MessageMetadata>Tokens: {message?.usage?.total_tokens}</MessageMetadata>
|
||||
return <MessageMetadata onClick={locateMessage}>Tokens: {message?.usage?.total_tokens}</MessageMetadata>
|
||||
}
|
||||
|
||||
if (generating) {
|
||||
@@ -19,7 +24,7 @@ const MessgeTokens: React.FC<{ message: Message }> = ({ message }) => {
|
||||
|
||||
if (message.role === 'assistant') {
|
||||
return (
|
||||
<MessageMetadata>
|
||||
<MessageMetadata onClick={locateMessage}>
|
||||
Tokens: {message?.usage?.total_tokens} | ↑{message?.usage?.prompt_tokens} | ↓{message?.usage?.completion_tokens}
|
||||
</MessageMetadata>
|
||||
)
|
||||
@@ -33,6 +38,7 @@ const MessageMetadata = styled.div`
|
||||
color: var(--color-text-2);
|
||||
user-select: text;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
export default MessgeTokens
|
||||
|
||||
@@ -131,7 +131,8 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
EventEmitter.on(EVENT_NAMES.AI_AUTO_RENAME, autoRenameTopic),
|
||||
EventEmitter.on(EVENT_NAMES.CLEAR_MESSAGES, () => {
|
||||
setMessages([])
|
||||
updateTopic({ ...topic, messages: [] })
|
||||
const defaultTopic = getDefaultTopic(assistant.id)
|
||||
updateTopic({ ...topic, name: defaultTopic.name, messages: [] })
|
||||
TopicManager.clearTopicMessages(topic.id)
|
||||
}),
|
||||
EventEmitter.on(EVENT_NAMES.EXPORT_TOPIC_IMAGE, async () => {
|
||||
|
||||
@@ -4,11 +4,9 @@ import AssistantSettingPopup from '@renderer/components/AssistantSettings'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { getDefaultTopic } from '@renderer/services/assistant'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Switch } from 'antd'
|
||||
@@ -24,8 +22,8 @@ interface Props {
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
}
|
||||
|
||||
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveTopic }) => {
|
||||
const { assistant, addTopic } = useAssistant(activeAssistant.id)
|
||||
const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
||||
const { assistant } = useAssistant(activeAssistant.id)
|
||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
const { topicPosition } = useSettings()
|
||||
@@ -33,13 +31,10 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveTopic }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const addNewTopic = useCallback(() => {
|
||||
const topic = getDefaultTopic(assistant.id)
|
||||
addTopic(topic)
|
||||
setActiveTopic(topic)
|
||||
db.topics.add({ id: topic.id, messages: [] })
|
||||
EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)
|
||||
window.message.success({ content: t('message.topic.added'), key: 'topic-added' })
|
||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
||||
}, [addTopic, assistant.id, setActiveTopic, t])
|
||||
}, [t])
|
||||
|
||||
return (
|
||||
<Navbar>
|
||||
|
||||
@@ -214,12 +214,12 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
onChange={(value) => setFontSizeValue(value)}
|
||||
onChangeComplete={(value) => dispatch(setFontSize(value))}
|
||||
min={12}
|
||||
max={18}
|
||||
max={22}
|
||||
step={1}
|
||||
marks={{
|
||||
12: <span style={{ fontSize: '12px' }}>A</span>,
|
||||
14: <span style={{ fontSize: '14px' }}>{t('common.default')}</span>,
|
||||
18: <span style={{ fontSize: '18px' }}>A</span>
|
||||
22: <span style={{ fontSize: '18px' }}>A</span>
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { CloseOutlined, DeleteOutlined, EditOutlined, FolderOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import {
|
||||
ClearOutlined,
|
||||
CloseOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
FolderOutlined,
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchMessagesSummary } from '@renderer/services/api'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import store, { useAppSelector } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Dropdown, MenuProps } from 'antd'
|
||||
import { findIndex } from 'lodash'
|
||||
@@ -31,11 +39,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
window.message.warning({ content: t('message.switch.disabled'), key: 'generating' })
|
||||
return
|
||||
}
|
||||
if (assistant.topics.length > 1) {
|
||||
const index = findIndex(assistant.topics, (t) => t.id === topic.id)
|
||||
setActiveTopic(assistant.topics[index + 1 === assistant.topics.length ? 0 : index + 1])
|
||||
removeTopic(topic)
|
||||
}
|
||||
const index = findIndex(assistant.topics, (t) => t.id === topic.id)
|
||||
setActiveTopic(assistant.topics[index + 1 === assistant.topics.length ? 0 : index + 1])
|
||||
removeTopic(topic)
|
||||
},
|
||||
[assistant.topics, generating, removeTopic, setActiveTopic, t]
|
||||
)
|
||||
@@ -64,6 +70,12 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
[generating, setActiveTopic, t]
|
||||
)
|
||||
|
||||
const onClearMessages = useCallback(() => {
|
||||
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true)
|
||||
store.dispatch(setGenerating(false))
|
||||
EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
||||
}, [])
|
||||
|
||||
const getTopicMenuItems = useCallback(
|
||||
(topic: Topic) => {
|
||||
const menus: MenuProps['items'] = [
|
||||
@@ -96,6 +108,18 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.topics.clear.title'),
|
||||
key: 'clear-messages',
|
||||
icon: <ClearOutlined />,
|
||||
async onClick() {
|
||||
window.modal.confirm({
|
||||
title: t('chat.input.clear.content'),
|
||||
centered: true,
|
||||
onOk: onClearMessages
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.topics.export.title'),
|
||||
key: 'export',
|
||||
@@ -138,7 +162,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
|
||||
return menus
|
||||
},
|
||||
[assistant, assistants, onDeleteTopic, onMoveTopic, t, updateTopic]
|
||||
[assistant, assistants, onClearMessages, onDeleteTopic, onMoveTopic, t, updateTopic]
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -150,11 +174,14 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||
<TopicListItem className={isActive ? 'active' : ''} onClick={() => onSwitchTopic(topic)}>
|
||||
<TopicName className="name">{topic.name.replace('`', '')}</TopicName>
|
||||
{assistant.topics.length > 1 && isActive && (
|
||||
{isActive && (
|
||||
<MenuButton
|
||||
className="menu"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (assistant.topics.length === 1) {
|
||||
return onClearMessages()
|
||||
}
|
||||
onDeleteTopic(topic)
|
||||
}}>
|
||||
<CloseOutlined />
|
||||
|
||||
@@ -6,7 +6,6 @@ import { isVisionModel } from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Button } from 'antd'
|
||||
import { upperFirst } from 'lodash'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -23,7 +22,8 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const onSelectModel = async () => {
|
||||
const onSelectModel = async (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.currentTarget.blur()
|
||||
const selectedModel = await SelectModelPopup.show({ model })
|
||||
if (selectedModel) {
|
||||
setModel(selectedModel)
|
||||
@@ -33,7 +33,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
return (
|
||||
<DropdownButton size="small" type="default" onClick={onSelectModel}>
|
||||
<ModelAvatar model={model} size={20} />
|
||||
<ModelName>{model ? upperFirst(model.name) : t('button.select_model')}</ModelName>
|
||||
<ModelName>{model ? model.name : t('button.select_model')}</ModelName>
|
||||
{isVisionModel(model) && <VisionIcon style={{ marginLeft: 0 }} />}
|
||||
</DropdownButton>
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId, hasModel } from '@renderer/services/model'
|
||||
import { Model } from '@renderer/types'
|
||||
import { Select } from 'antd'
|
||||
import { find, sortBy, upperFirst } from 'lodash'
|
||||
import { find, sortBy } from 'lodash'
|
||||
import { FC, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -23,7 +23,7 @@ const ModelSettings: FC = () => {
|
||||
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||
title: p.name,
|
||||
options: sortBy(p.models, 'name').map((m) => ({
|
||||
label: upperFirst(m.name),
|
||||
label: m.name,
|
||||
value: getModelUniqId(m)
|
||||
}))
|
||||
}))
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
}
|
||||
})
|
||||
}
|
||||
if (file.type === FileTypes.TEXT) {
|
||||
if ([FileTypes.TEXT, FileTypes.DOCUMENT].includes(file.type)) {
|
||||
const fileContent = await (await window.api.file.read(file.id + file.ext)).trim()
|
||||
parts.push({
|
||||
type: 'text',
|
||||
|
||||
@@ -40,7 +40,7 @@ export default class GeminiProvider extends BaseProvider {
|
||||
}
|
||||
} as InlineDataPart)
|
||||
}
|
||||
if (file.type === FileTypes.TEXT) {
|
||||
if ([FileTypes.TEXT, FileTypes.DOCUMENT].includes(file.type)) {
|
||||
const fileContent = await (await window.api.file.read(file.id + file.ext)).trim()
|
||||
parts.push({
|
||||
text: file.origin_name + '\n' + fileContent
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
|
||||
if (this.isNotSupportFiles) {
|
||||
if (message.files) {
|
||||
const textFiles = message.files.filter((file) => file.type === FileTypes.TEXT)
|
||||
const textFiles = message.files.filter((file) => [FileTypes.TEXT, FileTypes.DOCUMENT].includes(file.type))
|
||||
|
||||
if (textFiles.length > 0) {
|
||||
let text = ''
|
||||
@@ -104,7 +104,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
image_url: { url: image.data }
|
||||
})
|
||||
}
|
||||
if (file.type === FileTypes.TEXT) {
|
||||
if ([FileTypes.TEXT, FileTypes.DOCUMENT].includes(file.type)) {
|
||||
const fileContent = await (await window.api.file.read(file.id + file.ext)).trim()
|
||||
parts.push({
|
||||
type: 'text',
|
||||
@@ -134,13 +134,16 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
userMessages.push(await this.getMessageParam(message, model))
|
||||
}
|
||||
|
||||
const isOpenAIo1 = model.id.includes('o1-')
|
||||
const isSupportStreamOutput = streamOutput && this.isSupportStreamOutput(model.id)
|
||||
|
||||
// @ts-ignore key is not typed
|
||||
const stream = await this.sdk.chat.completions.create({
|
||||
model: model.id,
|
||||
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
|
||||
temperature: assistant?.settings?.temperature,
|
||||
messages: [isOpenAIo1 ? undefined : systemMessage, ...userMessages].filter(
|
||||
Boolean
|
||||
) as ChatCompletionMessageParam[],
|
||||
temperature: isOpenAIo1 ? 1 : assistant?.settings?.temperature,
|
||||
max_tokens: maxTokens,
|
||||
keep_alive: this.keepAliveTime,
|
||||
stream: isSupportStreamOutput
|
||||
|
||||
@@ -29,9 +29,6 @@ export async function restore() {
|
||||
data = JSON.parse(await window.api.decompress(file.content))
|
||||
}
|
||||
|
||||
// 处理文件内容
|
||||
console.log('Parsed file content:', data)
|
||||
|
||||
await handleData(data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
@@ -19,5 +19,6 @@ export const EVENT_NAMES = {
|
||||
NEW_CONTEXT: 'NEW_CONTEXT',
|
||||
NEW_BRANCH: 'NEW_BRANCH',
|
||||
EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE',
|
||||
LOCATE_MESSAGE: 'LOCATE_MESSAGE'
|
||||
LOCATE_MESSAGE: 'LOCATE_MESSAGE',
|
||||
ADD_NEW_TOPIC: 'ADD_NEW_TOPIC'
|
||||
}
|
||||
|
||||
@@ -17,10 +17,8 @@ async function getFileContent(file: FileType) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const fileId = file.id + file.ext
|
||||
|
||||
if (file.type === FileTypes.TEXT) {
|
||||
return await window.api.file.read(fileId)
|
||||
return await window.api.file.read(file.id + file.ext)
|
||||
}
|
||||
|
||||
return ''
|
||||
|
||||
@@ -143,10 +143,10 @@ const initialState: LlmState = {
|
||||
},
|
||||
{
|
||||
id: 'dashscope',
|
||||
name: 'DashScope',
|
||||
name: 'Bailian',
|
||||
apiKey: '',
|
||||
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||
models: SYSTEM_MODELS.dashscope,
|
||||
models: SYSTEM_MODELS.bailian,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
|
||||
@@ -182,7 +182,7 @@ const migrateConfig = {
|
||||
name: 'DashScope',
|
||||
apiKey: '',
|
||||
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||
models: SYSTEM_MODELS.dashscope,
|
||||
models: SYSTEM_MODELS.bailian,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
|
||||
61
src/renderer/src/utils/download.ts
Normal file
61
src/renderer/src/utils/download.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export const download = (url: string) => {
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
// 尝试从Content-Disposition头获取文件名
|
||||
const contentDisposition = response.headers.get('Content-Disposition')
|
||||
let filename = 'download' // 默认文件名
|
||||
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i)
|
||||
if (filenameMatch) {
|
||||
filename = filenameMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果URL中有文件名,使用URL中的文件名
|
||||
const urlFilename = url.split('/').pop()
|
||||
if (urlFilename && urlFilename.includes('.')) {
|
||||
filename = urlFilename
|
||||
}
|
||||
|
||||
// 如果文件名没有后缀,根据Content-Type添加后缀
|
||||
if (!filename.includes('.')) {
|
||||
const contentType = response.headers.get('Content-Type')
|
||||
const extension = getExtensionFromMimeType(contentType)
|
||||
filename += extension
|
||||
}
|
||||
|
||||
// 添加时间戳以确保文件名唯一
|
||||
const timestamp = Date.now()
|
||||
const finalFilename = `${timestamp}_${filename}`
|
||||
|
||||
return response.blob().then((blob) => ({ blob, finalFilename }))
|
||||
})
|
||||
.then(({ blob, finalFilename }) => {
|
||||
const blobUrl = URL.createObjectURL(new Blob([blob]))
|
||||
const link = document.createElement('a')
|
||||
link.href = blobUrl
|
||||
link.download = finalFilename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
link.remove()
|
||||
})
|
||||
}
|
||||
|
||||
// 辅助函数:根据MIME类型获取文件扩展名
|
||||
function getExtensionFromMimeType(mimeType: string | null): string {
|
||||
if (!mimeType) return '.bin' // 默认二进制文件扩展名
|
||||
|
||||
const mimeToExtension: { [key: string]: string } = {
|
||||
'image/jpeg': '.jpg',
|
||||
'image/png': '.png',
|
||||
'image/gif': '.gif',
|
||||
'application/pdf': '.pdf',
|
||||
'text/plain': '.txt',
|
||||
'application/msword': '.doc',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx'
|
||||
}
|
||||
|
||||
return mimeToExtension[mimeType] || '.bin'
|
||||
}
|
||||
@@ -32,3 +32,14 @@ $$
|
||||
return match
|
||||
})
|
||||
}
|
||||
|
||||
export function extractTitle(html: string): string | null {
|
||||
const titleRegex = /<title>(.*?)<\/title>/i
|
||||
const match = html.match(titleRegex)
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].trim()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
279
yarn.lock
279
yarn.lock
@@ -2227,7 +2227,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xmldom/xmldom@npm:^0.8.8":
|
||||
"@xmldom/xmldom@npm:^0.8.10, @xmldom/xmldom@npm:^0.8.8":
|
||||
version: 0.8.10
|
||||
resolution: "@xmldom/xmldom@npm:0.8.10"
|
||||
checksum: 10c0/c7647c442502720182b0d65b17d45d2d95317c1c8c497626fe524bda79b4fb768a9aa4fae2da919f308e7abcff7d67c058b102a9d641097e9a57f0b80187851f
|
||||
@@ -2287,6 +2287,7 @@ __metadata:
|
||||
localforage: "npm:^1.10.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
mime: "npm:^4.0.4"
|
||||
officeparser: "npm:^4.1.1"
|
||||
openai: "npm:^4.52.1"
|
||||
prettier: "npm:^3.2.4"
|
||||
react: "npm:^18.2.0"
|
||||
@@ -2941,6 +2942,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bl@npm:^1.0.0":
|
||||
version: 1.2.3
|
||||
resolution: "bl@npm:1.2.3"
|
||||
dependencies:
|
||||
readable-stream: "npm:^2.3.5"
|
||||
safe-buffer: "npm:^5.1.1"
|
||||
checksum: 10c0/ee6478864d3b1295614f269f3fbabeb2362a2f2fc7f8dc2f6c1f944a278d84e0572ecefd6d0b0736d7418763f98dc3b2738253191ea9e98e4b08de211cfac0a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bluebird-lst@npm:^1.0.9":
|
||||
version: 1.0.9
|
||||
resolution: "bluebird-lst@npm:1.0.9"
|
||||
@@ -3022,6 +3033,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-alloc-unsafe@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "buffer-alloc-unsafe@npm:1.1.0"
|
||||
checksum: 10c0/06b9298c9369621a830227c3797ceb3ff5535e323946d7b39a7398fed8b3243798259b3c85e287608c5aad35ccc551cec1a0a5190cc8f39652e8eee25697fc9c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-alloc@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "buffer-alloc@npm:1.2.0"
|
||||
dependencies:
|
||||
buffer-alloc-unsafe: "npm:^1.1.0"
|
||||
buffer-fill: "npm:^1.0.0"
|
||||
checksum: 10c0/09d87dd53996342ccfbeb2871257d8cdb25ce9ee2259adc95c6490200cd6e528c5fbae8f30bcc323fe8d8efb0fe541e4ac3bbe9ee3f81c6b7c4b27434cc02ab4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-crc32@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "buffer-crc32@npm:1.0.0"
|
||||
@@ -3050,6 +3078,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-fill@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "buffer-fill@npm:1.0.0"
|
||||
checksum: 10c0/55b5654fbbf2d7ceb4991bb537f5e5b5b5b9debca583fee416a74fcec47c16d9e7a90c15acd27577da7bd750b7fa6396e77e7c221e7af138b6d26242381c6e4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-from@npm:^1.0.0":
|
||||
version: 1.1.2
|
||||
resolution: "buffer-from@npm:1.1.2"
|
||||
@@ -3057,7 +3092,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer@npm:^5.1.0, buffer@npm:^5.2.0":
|
||||
"buffer@npm:^5.1.0, buffer@npm:^5.2.0, buffer@npm:^5.2.1":
|
||||
version: 5.7.1
|
||||
resolution: "buffer@npm:5.7.1"
|
||||
dependencies:
|
||||
@@ -3489,6 +3524,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^2.8.1":
|
||||
version: 2.20.3
|
||||
resolution: "commander@npm:2.20.3"
|
||||
checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^5.0.0":
|
||||
version: 5.1.0
|
||||
resolution: "commander@npm:5.1.0"
|
||||
@@ -3843,6 +3885,69 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-tar@npm:^4.0.0, decompress-tar@npm:^4.1.0, decompress-tar@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "decompress-tar@npm:4.1.1"
|
||||
dependencies:
|
||||
file-type: "npm:^5.2.0"
|
||||
is-stream: "npm:^1.1.0"
|
||||
tar-stream: "npm:^1.5.2"
|
||||
checksum: 10c0/92d86c5dfe2a89f9b5db584668f8ed2a3107339083872c7f78b5f7d55222d954545e018c10346a50991542ad6d1406128bf1c97a24f023810993a1dcfb3c3f21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-tarbz2@npm:^4.0.0":
|
||||
version: 4.1.1
|
||||
resolution: "decompress-tarbz2@npm:4.1.1"
|
||||
dependencies:
|
||||
decompress-tar: "npm:^4.1.0"
|
||||
file-type: "npm:^6.1.0"
|
||||
is-stream: "npm:^1.1.0"
|
||||
seek-bzip: "npm:^1.0.5"
|
||||
unbzip2-stream: "npm:^1.0.9"
|
||||
checksum: 10c0/d5ab2c2435a53f45da8348ffdb5ae0a3ff8fec55948b7890a1c55413de4d1e539a22978e7dcd8bd3561985878c9778253fe146cbdea429f04fa4529abb57c54e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-targz@npm:^4.0.0":
|
||||
version: 4.1.1
|
||||
resolution: "decompress-targz@npm:4.1.1"
|
||||
dependencies:
|
||||
decompress-tar: "npm:^4.1.1"
|
||||
file-type: "npm:^5.2.0"
|
||||
is-stream: "npm:^1.1.0"
|
||||
checksum: 10c0/42514fb2df6248c56b2b115494b7d1d046bc582e960354ba4faad5792f261782a61d17d9ef53845abe78c0f0ecafc195cb0754c00227fa0bd0642a1bfd8eafad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-unzip@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "decompress-unzip@npm:4.0.1"
|
||||
dependencies:
|
||||
file-type: "npm:^3.8.0"
|
||||
get-stream: "npm:^2.2.0"
|
||||
pify: "npm:^2.3.0"
|
||||
yauzl: "npm:^2.4.2"
|
||||
checksum: 10c0/896f88e1c23b59cdce022227a8910c06158bd4b296c21d61af7167bd50d00e9e4355b605bdbfd7ba75d46ad277d4f881cdd037aec7165a40ccd0ee4ef59443a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress@npm:^4.2.0":
|
||||
version: 4.2.1
|
||||
resolution: "decompress@npm:4.2.1"
|
||||
dependencies:
|
||||
decompress-tar: "npm:^4.0.0"
|
||||
decompress-tarbz2: "npm:^4.0.0"
|
||||
decompress-targz: "npm:^4.0.0"
|
||||
decompress-unzip: "npm:^4.0.1"
|
||||
graceful-fs: "npm:^4.1.10"
|
||||
make-dir: "npm:^1.0.0"
|
||||
pify: "npm:^2.3.0"
|
||||
strip-dirs: "npm:^2.0.0"
|
||||
checksum: 10c0/6730279fa206aad04a8338a88ab49c596034c502b2d5f23a28d0a28290b82d9217f9e60c8b5739805474ca842fc856e08e2d64ed759f2118c2bcabe42fa9eece
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-is@npm:^0.1.3":
|
||||
version: 0.1.4
|
||||
resolution: "deep-is@npm:0.1.4"
|
||||
@@ -4295,7 +4400,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"end-of-stream@npm:^1.1.0":
|
||||
"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0":
|
||||
version: 1.4.4
|
||||
resolution: "end-of-stream@npm:1.4.4"
|
||||
dependencies:
|
||||
@@ -4993,6 +5098,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:^3.8.0":
|
||||
version: 3.9.0
|
||||
resolution: "file-type@npm:3.9.0"
|
||||
checksum: 10c0/7ae074b350c2300807a99d428600a8ee6b2ace901400898706a20ddc2c43c9abb7e05177ff55ed67a2fd26dfa9b91857b21ec9c0ab3202b9cabebc7e65900240
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "file-type@npm:5.2.0"
|
||||
checksum: 10c0/c16c2f4e484a838c12b63e08637277905f08aebb1afbc291086029210aea17ded5ed701c9a4588313446ae0c1da71566b58df9a9c758a1ec300c4f80b9713cbf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:^6.1.0":
|
||||
version: 6.2.0
|
||||
resolution: "file-type@npm:6.2.0"
|
||||
checksum: 10c0/3d7fe85a10bd97ca0c35fd9a20d21f5b20849bbb70985d37c34475051433f3c6109c76a3e5893bff6773037b769be9730a2db762789ecf25def9b62a4c2ee953
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-url@npm:^2.0.0":
|
||||
version: 2.0.2
|
||||
resolution: "file-url@npm:2.0.2"
|
||||
@@ -5147,6 +5273,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-constants@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "fs-constants@npm:1.0.0"
|
||||
checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "fs-extra@npm:1.0.0"
|
||||
@@ -5307,6 +5440,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^2.2.0":
|
||||
version: 2.3.1
|
||||
resolution: "get-stream@npm:2.3.1"
|
||||
dependencies:
|
||||
object-assign: "npm:^4.0.1"
|
||||
pinkie-promise: "npm:^2.0.0"
|
||||
checksum: 10c0/46c12f496e7edec688a1cc570fe7556ce91e91201fa7efb146853fb9f0a8f0b0bb9a02cf9d9e4e9d4e2097f98c83b09621d9034c25ca0cf80ae6f4dace9c3465
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^5.1.0":
|
||||
version: 5.2.0
|
||||
resolution: "get-stream@npm:5.2.0"
|
||||
@@ -5497,7 +5640,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
|
||||
"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
|
||||
version: 4.2.11
|
||||
resolution: "graceful-fs@npm:4.2.11"
|
||||
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
|
||||
@@ -6329,6 +6472,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-natural-number@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "is-natural-number@npm:4.0.1"
|
||||
checksum: 10c0/f05c544cb0ad39d4410e2ae2244282bf61918ebbb808b665436ffca4f6bbe908d3ae3a8d21fe143d302951f157d969986dd432098b63899561639fcd1ce1c280
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-negative-zero@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "is-negative-zero@npm:2.0.3"
|
||||
@@ -6413,7 +6563,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-stream@npm:^1.0.1":
|
||||
"is-stream@npm:^1.0.1, is-stream@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "is-stream@npm:1.1.0"
|
||||
checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002
|
||||
@@ -7087,6 +7237,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-dir@npm:^1.0.0":
|
||||
version: 1.3.0
|
||||
resolution: "make-dir@npm:1.3.0"
|
||||
dependencies:
|
||||
pify: "npm:^3.0.0"
|
||||
checksum: 10c0/5eb94f47d7ef41d89d1b8eef6539b8950d5bd99eeba093a942bfd327faa37d2d62227526b88b73633243a2ec7972d21eb0f4e5d62ae4e02a79e389f4a7bb3022
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-fetch-happen@npm:^13.0.0":
|
||||
version: 13.0.1
|
||||
resolution: "make-fetch-happen@npm:13.0.1"
|
||||
@@ -8053,6 +8212,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-ensure@npm:^0.0.0":
|
||||
version: 0.0.0
|
||||
resolution: "node-ensure@npm:0.0.0"
|
||||
checksum: 10c0/7af391aee024a8b7df77c239ed8b90417e3f2539824fa06b60f243ce14c75ee455766464c7c3ba9407d5b1e4d1d74ed5cf5f8af10c67b0fc05aa6e29f5d2462b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.7":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
@@ -8152,7 +8318,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.1.1":
|
||||
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
|
||||
@@ -8219,6 +8385,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"officeparser@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "officeparser@npm:4.1.1"
|
||||
dependencies:
|
||||
"@xmldom/xmldom": "npm:^0.8.10"
|
||||
decompress: "npm:^4.2.0"
|
||||
file-type: "npm:^16.5.4"
|
||||
node-ensure: "npm:^0.0.0"
|
||||
rimraf: "npm:^2.6.3"
|
||||
bin:
|
||||
officeparser: officeParser.js
|
||||
checksum: 10c0/7c5f01b3fe74fd31ca6ae6a6f46b2336d953eece3ca61d171e417b50d7e1962268af630bef0c8d40e39a1c471e7146281eecd880f0eecc576895eee9bc20b6a7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"omggif@npm:^1.0.10, omggif@npm:^1.0.9":
|
||||
version: 1.0.10
|
||||
resolution: "omggif@npm:1.0.10"
|
||||
@@ -8610,13 +8791,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pify@npm:^2.0.0":
|
||||
"pify@npm:^2.0.0, pify@npm:^2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "pify@npm:2.3.0"
|
||||
checksum: 10c0/551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pify@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "pify@npm:3.0.0"
|
||||
checksum: 10c0/fead19ed9d801f1b1fcd0638a1ac53eabbb0945bf615f2f8806a8b646565a04a1b0e7ef115c951d225f042cca388fdc1cd3add46d10d1ed6951c20bd2998af10
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pinkie-promise@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "pinkie-promise@npm:2.0.1"
|
||||
@@ -9664,7 +9852,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6":
|
||||
"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6":
|
||||
version: 2.3.8
|
||||
resolution: "readable-stream@npm:2.3.8"
|
||||
dependencies:
|
||||
@@ -10073,6 +10261,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^2.6.3":
|
||||
version: 2.7.1
|
||||
resolution: "rimraf@npm:2.7.1"
|
||||
dependencies:
|
||||
glob: "npm:^7.1.3"
|
||||
bin:
|
||||
rimraf: ./bin.js
|
||||
checksum: 10c0/4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "rimraf@npm:3.0.2"
|
||||
@@ -10196,7 +10395,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:~5.2.0":
|
||||
"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:~5.2.0":
|
||||
version: 5.2.1
|
||||
resolution: "safe-buffer@npm:5.2.1"
|
||||
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
|
||||
@@ -10284,6 +10483,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"seek-bzip@npm:^1.0.5":
|
||||
version: 1.0.6
|
||||
resolution: "seek-bzip@npm:1.0.6"
|
||||
dependencies:
|
||||
commander: "npm:^2.8.1"
|
||||
bin:
|
||||
seek-bunzip: bin/seek-bunzip
|
||||
seek-table: bin/seek-bzip-table
|
||||
checksum: 10c0/e4019e4498bb725ff855603595c4116ca003674b13d29cb9ed9891b2ceec884ccf7c1cb5dba0d6b50fe6ce760a011078f5744efb79823f4ddb9decb1571e9912
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver-compare@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "semver-compare@npm:1.0.0"
|
||||
@@ -10776,6 +10987,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-dirs@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "strip-dirs@npm:2.1.0"
|
||||
dependencies:
|
||||
is-natural-number: "npm:^4.0.1"
|
||||
checksum: 10c0/073d6d08331ec2e87afc2c2535d7336fee1d63797384045e4ecb9908a5ac6615022ee000cc278d6bbc94147bed7350f7cf4657b6d18c377813f37e7ae329fb52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-json-comments@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "strip-json-comments@npm:3.1.1"
|
||||
@@ -10908,6 +11128,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-stream@npm:^1.5.2":
|
||||
version: 1.6.2
|
||||
resolution: "tar-stream@npm:1.6.2"
|
||||
dependencies:
|
||||
bl: "npm:^1.0.0"
|
||||
buffer-alloc: "npm:^1.2.0"
|
||||
end-of-stream: "npm:^1.0.0"
|
||||
fs-constants: "npm:^1.0.0"
|
||||
readable-stream: "npm:^2.3.0"
|
||||
to-buffer: "npm:^1.1.1"
|
||||
xtend: "npm:^4.0.0"
|
||||
checksum: 10c0/ab8528d2cc9ccd0906d1ce6d8089030b2c92a578c57645ff4971452c8c5388b34c7152c04ed64b8510d22a66ffaf0fee58bada7d6ab41ad1e816e31993d59cf3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-stream@npm:^3.0.0":
|
||||
version: 3.1.7
|
||||
resolution: "tar-stream@npm:3.1.7"
|
||||
@@ -10982,6 +11217,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"through@npm:^2.3.8":
|
||||
version: 2.3.8
|
||||
resolution: "through@npm:2.3.8"
|
||||
checksum: 10c0/4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"timm@npm:^1.6.1":
|
||||
version: 1.7.1
|
||||
resolution: "timm@npm:1.7.1"
|
||||
@@ -11026,6 +11268,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"to-buffer@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "to-buffer@npm:1.1.1"
|
||||
checksum: 10c0/fb9fc6a0103f2b06e2e01c3d291586d0148759d5584f35d0973376434d1b58bd6ee5df9273f0bb1190eb2a5747c894bf49fed571325a7ac10208a48f31736439
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"to-fast-properties@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "to-fast-properties@npm:2.0.0"
|
||||
@@ -11280,6 +11529,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unbzip2-stream@npm:^1.0.9":
|
||||
version: 1.4.3
|
||||
resolution: "unbzip2-stream@npm:1.4.3"
|
||||
dependencies:
|
||||
buffer: "npm:^5.2.1"
|
||||
through: "npm:^2.3.8"
|
||||
checksum: 10c0/2ea2048f3c9db3499316ccc1d95ff757017ccb6f46c812d7c42466247e3b863fb178864267482f7f178254214247779daf68e85f50bd7736c3c97ba2d58b910a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~5.26.4":
|
||||
version: 5.26.5
|
||||
resolution: "undici-types@npm:5.26.5"
|
||||
@@ -12101,7 +12360,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yauzl@npm:^2.10.0":
|
||||
"yauzl@npm:^2.10.0, yauzl@npm:^2.4.2":
|
||||
version: 2.10.0
|
||||
resolution: "yauzl@npm:2.10.0"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user