Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb3eb7d90 | ||
|
|
64ad2fc9f4 | ||
|
|
7f0909c796 | ||
|
|
27631d9cff | ||
|
|
596cf8e3f2 | ||
|
|
6e2ab66b81 | ||
|
|
2cbb4c8831 | ||
|
|
5347f63aa8 | ||
|
|
077a66c675 | ||
|
|
6e7b6d8387 | ||
|
|
bdf6df1936 | ||
|
|
a2dd440f77 | ||
|
|
b47d6c95e7 | ||
|
|
6265d27ebc | ||
|
|
0dd60cb129 | ||
|
|
04dae10d89 | ||
|
|
71ef0f319f | ||
|
|
58817ae82f | ||
|
|
7e477cb9c7 | ||
|
|
1063610c01 | ||
|
|
927670d3a3 | ||
|
|
2fea7659b1 | ||
|
|
43b9298329 | ||
|
|
fe2e3bfc36 | ||
|
|
bae80fda8d | ||
|
|
ab709b9c61 | ||
|
|
2c28e3bb76 | ||
|
|
d98020e12c | ||
|
|
25addc390f | ||
|
|
88d04a1a6e | ||
|
|
1f582c672d | ||
|
|
c913b2a6d0 | ||
|
|
267c60f24d | ||
|
|
a8ccaf6847 | ||
|
|
a3a005b946 | ||
|
|
2220a6016e | ||
|
|
3197390f1a | ||
|
|
5f04d1adb1 | ||
|
|
76b6593545 | ||
|
|
04ce641bf7 | ||
|
|
31e912aac3 | ||
|
|
832ec99d92 | ||
|
|
ef9fda6d0c | ||
|
|
624230411a | ||
|
|
14808649f8 | ||
|
|
3cc8cfb43b | ||
|
|
4055111ade | ||
|
|
dc98b27e3e | ||
|
|
90fec317e5 | ||
|
|
303a0e20a0 | ||
|
|
d69252a7da | ||
|
|
99f05383cb | ||
|
|
5ba6c9f882 | ||
|
|
27f64409d6 | ||
|
|
7237729ff6 | ||
|
|
d29cd3c657 | ||
|
|
8c87f59822 | ||
|
|
5780141df4 | ||
|
|
f5799ef47b | ||
|
|
6f502049f4 | ||
|
|
c68ad4febb | ||
|
|
2ebcec9f59 | ||
|
|
7bc74a5b86 | ||
|
|
75152421d9 | ||
|
|
3326074076 | ||
|
|
362d82bdcc | ||
|
|
fcce241c82 | ||
|
|
693b06c126 | ||
|
|
c310c71576 | ||
|
|
bea95fc52f | ||
|
|
969cf8ea21 | ||
|
|
5b357f14e5 | ||
|
|
de5db4f805 | ||
|
|
1ccb5edda7 | ||
|
|
97b8749dd1 | ||
|
|
a6d7ecae81 | ||
|
|
938efb5aef | ||
|
|
9baf0f772e | ||
|
|
ff5de3625e | ||
|
|
f1cfdb29f8 | ||
|
|
26e48f07fd | ||
|
|
8bb5fb9811 | ||
|
|
d41667b599 | ||
|
|
85152cbcd7 | ||
|
|
b80863111f | ||
|
|
6cd88fa51d | ||
|
|
3619e8f47b | ||
|
|
5b41dd24d4 | ||
|
|
91dd2f233a | ||
|
|
7e651f9abc | ||
|
|
e44f666c5c | ||
|
|
c254b52b51 | ||
|
|
37c3a4438f | ||
|
|
302d7511dc | ||
|
|
68d57ba238 | ||
|
|
cf98675223 | ||
|
|
4cc140e4f2 | ||
|
|
2da3a3f010 | ||
|
|
fa6f7ecab0 | ||
|
|
31ab444300 | ||
|
|
85453f5a3a | ||
|
|
6d92539524 | ||
|
|
a605ae6043 |
1
.github/workflows/release.yml
vendored
@@ -71,5 +71,6 @@ jobs:
|
|||||||
dist/*.rpm
|
dist/*.rpm
|
||||||
dist/*.tar.gz
|
dist/*.tar.gz
|
||||||
dist/latest*.yml
|
dist/latest*.yml
|
||||||
|
dist/*.blockmap
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|||||||
6
.gitignore
vendored
@@ -19,12 +19,6 @@ lerna-debug.log*
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# NPM
|
|
||||||
npm/*/*
|
|
||||||
!npm/*/dist
|
|
||||||
!npm/*/package.json
|
|
||||||
!npm/*/*.js
|
|
||||||
|
|
||||||
# Yarn
|
# Yarn
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
.yarn/*
|
||||||
|
|||||||
29
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
136
LICENSE
@@ -1,101 +1,79 @@
|
|||||||
### Cherry Studio 商业许可协议
|
## Cherry Studio 用户协议
|
||||||
|
|
||||||
|
欢迎使用 Cherry Studio 桌面 AI 客户端工具。请仔细阅读以下协议条款,继续使用本软件即表示您同意本协议内容。
|
||||||
|
|
||||||
|
**许可协议**
|
||||||
|
|
||||||
|
本软件采用 Apache License 2.0 许可。除 Apache License 2.0 规定的条款外,您在使用 Cherry Studio 时还应遵守以下附加条款:
|
||||||
|
|
||||||
|
**一. 商用许可**
|
||||||
|
|
||||||
|
1. **免费商用**:用户在不修改代码的情况下,可以免费用于商业目的。
|
||||||
|
2. **商业授权**:如果您满足以下任意条件之一,需取得商业授权:
|
||||||
|
1. 对本软件进行二次修改、开发(包括但不限于修改应用名称、logo、代码以及功能)。
|
||||||
|
2. 为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。
|
||||||
|
3. 预装或集成到硬件设备或产品中进行捆绑销售。
|
||||||
|
4. 政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。
|
||||||
|
|
||||||
|
**二. 贡献者协议**
|
||||||
|
|
||||||
|
作为 Cherry Studio 的贡献者,您应当同意以下条款:
|
||||||
|
|
||||||
|
1. **许可调整**:生产者有权根据需要对开源协议进行调整,使其更加严格或宽松。
|
||||||
|
2. **商业用途**:您贡献的代码可能会被用于商业用途,包括但不限于云业务运营。
|
||||||
|
|
||||||
|
**三. 其他条款**
|
||||||
|
|
||||||
|
1. 本协议条款的解释权归 Cherry Studio 开发者所有。
|
||||||
|
2. 本协议可能根据实际情况进行更新,更新时将通过本软件通知用户。
|
||||||
|
|
||||||
|
如有任何问题或需申请商业授权,请联系 Cherry Studio 开发团队。
|
||||||
|
|
||||||
|
除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。有关 Apache License 2.0 的详细信息,请访问 http://www.apache.org/licenses/LICENSE-2.0。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 中文版
|
根据 Apache 许可证 2.0 版(“许可证”)进行许可;除非符合许可证,否则您不得使用此文件。您可以在以下网址获取许可证副本:
|
||||||
|
|
||||||
**Cherry Studio 商业许可协议**
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
本协议(以下简称“协议”)由以下双方签订:
|
除非适用法律要求或书面同意,软件根据许可证分发的内容以“原样”分发,不附带任何明示或暗示的保证或条件。请参阅特定语言管理权限的许可证和许可证下的限制。
|
||||||
|
|
||||||
- 许可方:王谦(kangfenmao@qq.com)
|
## Cherry Studio User Agreement
|
||||||
- 被许可方:[被许可方名称]
|
|
||||||
|
|
||||||
**1. 定义**
|
Welcome to Cherry Studio, a desktop AI client tool. Please read the following agreement carefully. By continuing to use this software, you agree to the terms outlined below.
|
||||||
|
|
||||||
- “软件”指 Cherry Studio 软件,网址为 https://cherry-ai.com。
|
**License Agreement**
|
||||||
- “商业用途”指任何以盈利为目的的使用。
|
|
||||||
|
|
||||||
**2. 许可**
|
This software is licensed under the **Apache License 2.0**. In addition to the terms of the Apache License 2.0, the following additional terms apply to the use of Cherry Studio:
|
||||||
|
|
||||||
- 未经许可方明确书面许可,被许可方不得将软件用于商业用途。
|
**I. Commercial Use License**
|
||||||
- 未经许可方事先书面同意,被许可方不得将软件全部或部分用于商业用途分发。
|
|
||||||
- 未经许可方明确授权,被许可方不得再许可、租赁、销售、出租或以其他方式将软件转让给任何第三方用于商业用途。
|
|
||||||
|
|
||||||
**3. 责任限制**
|
1. **Free Commercial Use**: Users can use the software for commercial purposes without modifying the code.
|
||||||
|
2. **Commercial License Required**: A commercial license is required if any of the following conditions are met:
|
||||||
|
1. You modify, develop, or alter the software, including but not limited to changes to the application name, logo, code, or functionality.
|
||||||
|
2. You provide multi-tenant services to enterprise customers with 10 or more users.
|
||||||
|
3. You pre-install or integrate the software into hardware devices or products and bundle it for sale.
|
||||||
|
4. You are engaging in large-scale procurement for government or educational institutions, especially involving security, data privacy, or other sensitive requirements.
|
||||||
|
|
||||||
开发者不对因使用本软件而产生的任何直接或间接损失承担责任。用户应自行承担使用本软件的风险。
|
**II. Contributor Agreement**
|
||||||
|
|
||||||
**4. 许可协议生效日期**
|
As a contributor to Cherry Studio, you agree to the following:
|
||||||
|
|
||||||
本许可协议自用户首次下载或使用本软件之日起生效。
|
1. **License Adjustment**: The producer reserves the right to adjust the open-source license as needed, making it stricter or more lenient.
|
||||||
|
2. **Commercial Use**: Any code you contribute may be used for commercial purposes, including but not limited to cloud business operations.
|
||||||
|
|
||||||
**5. 许可终止**
|
**III. Other Terms**
|
||||||
|
|
||||||
如发现用户违反上述条款,开发者有权随时终止本许可,并要求用户停止使用本软件及删除所有相关副本。
|
1. The interpretation of these terms is subject to the discretion of Cherry Studio developers.
|
||||||
|
2. These terms may be updated, and users will be notified through the software when changes occur.
|
||||||
|
|
||||||
**6. 其他**
|
For any questions or to request a commercial license, please contact the Cherry Studio development team.
|
||||||
|
|
||||||
本协议的解释、效力及争议的解决,均适用中华人民共和国法律。
|
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
**7. 联系信息**
|
|
||||||
|
|
||||||
- 许可方联系方式:
|
|
||||||
- 手机号:18539907620
|
|
||||||
- 邮箱:kangfenmao@qq.com
|
|
||||||
|
|
||||||
**许可方(签字):**
|
|
||||||
**日期:**
|
|
||||||
|
|
||||||
**被许可方(签字):**
|
|
||||||
**日期:**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### English Version
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
**Cherry Studio Commercial License Agreement**
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
This Agreement ("Agreement") is entered into by and between:
|
|
||||||
|
|
||||||
- Licensor: Wang Qian (kangfenmao)
|
|
||||||
- Licensee: [Licensee Name]
|
|
||||||
|
|
||||||
**1. Definitions**
|
|
||||||
|
|
||||||
- "Software" refers to the Cherry Studio software, available at https://cherry-ai.com.
|
|
||||||
- "Commercial Use" refers to any use for profit.
|
|
||||||
|
|
||||||
**2. License**
|
|
||||||
|
|
||||||
- The Licensee may not use the Software for Commercial Use without the Licensor's explicit written permission.
|
|
||||||
- The Licensee may not distribute the Software in whole or in part for Commercial Use without the Licensor's prior written consent.
|
|
||||||
- The Licensee may not sublicense, lease, sell, rent, or otherwise transfer the Software to any third party for Commercial Use without the Licensor's explicit authorization.
|
|
||||||
|
|
||||||
**3. Termination of License**
|
|
||||||
|
|
||||||
The developer reserves the right to terminate this license at any time if the terms are violated, and may require the user to cease using the software and delete all related copies.
|
|
||||||
|
|
||||||
**4. Effective Date of License Agreement**
|
|
||||||
|
|
||||||
This license agreement becomes effective from the date the user first downloads or uses the software.
|
|
||||||
|
|
||||||
**5. Termination of License**
|
|
||||||
|
|
||||||
The developer reserves the right to terminate this license at any time if the terms are violated, and may require the user to cease using the software and delete all related copies.
|
|
||||||
|
|
||||||
**6. Miscellaneous**
|
|
||||||
|
|
||||||
This Agreement shall be governed by and construed in accordance with the laws of the People's Republic of China.
|
|
||||||
|
|
||||||
**7. Contact Information**
|
|
||||||
|
|
||||||
- Licensor's Contact Details:
|
|
||||||
- Phone: 18539907620
|
|
||||||
- Email: kangfenmao@qq.com
|
|
||||||
|
|
||||||
**Licensor (Signature):**
|
|
||||||
**Date:**
|
|
||||||
|
|
||||||
**Licensee (Signature):**
|
|
||||||
**Date:**
|
|
||||||
|
|||||||
28
README.md
@@ -1,22 +1,32 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/kangfenmao/cherry-studio/releases">
|
<a href="https://github.com/kangfenmao/cherry-studio/releases">
|
||||||
<img src="https://github.com/user-attachments/assets/7b4f2f78-5cbe-4be8-9aec-f98d8405a505" alt="banner" />
|
<img src="https://github.com/kangfenmao/cherry-studio/blob/main/build/icon.png?raw=true" width="150" height="150" alt="banner" />
|
||||||
</a>
|
</a>
|
||||||
English | <a href="./docs/README.zh.md">中文</a>
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
English | <a href="./docs/README.zh.md">中文</a> | <a href="./docs/README.ja.md">日本語</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# 🍒 Cherry Studio
|
# 🍒 Cherry Studio
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Cherry Studio is a desktop client that supports for multiple LLM providers, available on Windows, Mac and Linux.
|
Cherry Studio is a desktop client that supports for multiple LLM providers, available on Windows, Mac and Linux.
|
||||||
|
|
||||||
|
👏 Join [Telegram Group](https://t.me/CherryStudioAI)
|
||||||
|
|
||||||
# 🌠 Screenshot
|
# 🌠 Screenshot
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
# 🌟 Features
|
# 🌟 Features
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://www.producthunt.com/posts/cherry-studio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cherry-studio" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=496640&theme=light" alt="Cherry Studio - AI Chatbots, AI Desktop Client | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
1. Support for Multiple LLM Providers.
|
1. Support for Multiple LLM Providers.
|
||||||
2. Allows creation of multiple Assistants.
|
2. Allows creation of multiple Assistants.
|
||||||
3. Enables creation of multiple topics.
|
3. Enables creation of multiple topics.
|
||||||
@@ -27,9 +37,9 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai
|
|||||||
|
|
||||||
# 🖥️ Develop
|
# 🖥️ Develop
|
||||||
|
|
||||||
## Recommended IDE Setup
|
## IDE Setup
|
||||||
|
|
||||||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
[VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||||
|
|
||||||
## Project Setup
|
## Project Setup
|
||||||
|
|
||||||
@@ -68,6 +78,10 @@ $ yarn build:linux
|
|||||||
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
|
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
# Community
|
||||||
|
|
||||||
|
[Telegram](https://t.me/CherryStudioAI)
|
||||||
|
|
||||||
# Sponsor
|
# Sponsor
|
||||||
|
|
||||||
[Buy Me a Coffee](docs/sponsor.md)
|
[Buy Me a Coffee](docs/sponsor.md)
|
||||||
|
|||||||
91
docs/README.ja.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/kangfenmao/cherry-studio/releases">
|
||||||
|
<img src="https://github.com/kangfenmao/cherry-studio/blob/main/build/icon.png?raw=true" width="150" height="150" alt="banner" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<a href="./README.md">English</a> | <a href="./README.zh.md">中文</a> | 日本語
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# 🍒 Cherry Studio
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Cherry Studioは、複数のLLMプロバイダーをサポートするデスクトップクライアントで、Windows、Mac、Linuxで利用可能です。
|
||||||
|
|
||||||
|
👏 [Telegramグループ](https://t.me/CherryStudioAI)に参加しましょう
|
||||||
|
|
||||||
|
# 🌠 スクリーンショット
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
# 🌟 特徴
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://www.producthunt.com/posts/cherry-studio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cherry-studio" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=496640&theme=light" alt="Cherry Studio - AI Chatbots, AI Desktop Client | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
1. 複数のLLMプロバイダーをサポート。
|
||||||
|
2. 複数のアシスタントを作成可能。
|
||||||
|
3. 複数のトピックを作成可能。
|
||||||
|
4. 同じ会話で複数のモデルを使用して質問に回答可能。
|
||||||
|
5. ドラッグアンドドロップでの並べ替えをサポート。
|
||||||
|
6. コードハイライト。
|
||||||
|
7. Mermaidチャート
|
||||||
|
|
||||||
|
# 🖥️ 開発
|
||||||
|
|
||||||
|
## IDEの設定
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||||
|
|
||||||
|
## プロジェクトの設定
|
||||||
|
|
||||||
|
### インストール
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 開発
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### ビルド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windowsの場合
|
||||||
|
$ yarn build:win
|
||||||
|
|
||||||
|
# macOSの場合
|
||||||
|
$ yarn build:mac
|
||||||
|
|
||||||
|
# Linuxの場合
|
||||||
|
$ yarn build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
# ⭐️ スター履歴
|
||||||
|
|
||||||
|
[](https://star-history.com/#kangfenmao/cherry-studio&Timeline)
|
||||||
|
|
||||||
|
# 🚀 コントリビューター
|
||||||
|
|
||||||
|
<a href="https://github.com/kangfenmao/cherry-studio/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
# コミュニティ
|
||||||
|
|
||||||
|
[Telegram](https://t.me/CherryStudioAI)
|
||||||
|
|
||||||
|
# スポンサー
|
||||||
|
|
||||||
|
[Buy Me a Coffee](docs/sponsor.md)
|
||||||
|
|
||||||
|
# 📃 ライセンス
|
||||||
|
|
||||||
|
[LICENSE](./LICENSE)
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/kangfenmao/cherry-studio/releases">
|
<a href="https://github.com/kangfenmao/cherry-studio/releases">
|
||||||
<img src="https://github.com/user-attachments/assets/995910f3-177a-4d1e-97ea-04e3b009ba36" alt="banner"/>
|
<img src="https://github.com/kangfenmao/cherry-studio/blob/main/build/icon.png?raw=true" width="150" height="150" alt="banner" />
|
||||||
</a>
|
</a>
|
||||||
English / <a href="https://github.com/kangfenmao/cherry-studio">中文</a>
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
中文 / <a href="https://github.com/kangfenmao/cherry-studio">English</a> / <a href="./README.ja.md">日本語</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# 🍒 Cherry Studio
|
# 🍒 Cherry Studio
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Cherry Studio 是一款跨平台桌面客户端,支持多个大语言模型(LLM)服务商,兼容 Windows、Mac 和 Linux 系统,并拥丰富的个性化选项与领先的功能设计。
|
Cherry Studio 是一款跨平台桌面客户端,支持多个大语言模型(LLM)服务商,兼容 Windows、Mac 和 Linux 系统,并拥丰富的个性化选项与领先的功能设计。
|
||||||
|
|
||||||
|
👏 欢迎加入 [Telegram 群组](https://t.me/CherryStudioAI)
|
||||||
|
|
||||||
# 🌠 界面
|
# 🌠 界面
|
||||||
|
|
||||||
<img width="1582" alt="Xnip2024-09-23_15-01-53" src="https://github.com/user-attachments/assets/554aa31b-87b6-49fe-877d-af313e1608b0">
|
<img width="1582" alt="Xnip2024-09-23_15-01-53" src="https://github.com/user-attachments/assets/554aa31b-87b6-49fe-877d-af313e1608b0">
|
||||||
@@ -20,6 +26,10 @@ Cherry Studio 是一款跨平台桌面客户端,支持多个大语言模型(
|
|||||||
|
|
||||||
# 🌟 特性
|
# 🌟 特性
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://www.producthunt.com/posts/cherry-studio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cherry-studio" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=496640&theme=light" alt="Cherry Studio - AI Chatbots, AI Desktop Client | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
## 😌 轻松上手
|
## 😌 轻松上手
|
||||||
|
|
||||||
🍏Windows,Mac,Linux跨平台支持
|
🍏Windows,Mac,Linux跨平台支持
|
||||||
@@ -52,9 +62,9 @@ Cherry Studio 是一款跨平台桌面客户端,支持多个大语言模型(
|
|||||||
|
|
||||||
# 🖥️ 开发指南
|
# 🖥️ 开发指南
|
||||||
|
|
||||||
## 推荐的开发环境
|
## 开发环境
|
||||||
|
|
||||||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
[VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||||
|
|
||||||
## 项目设置
|
## 项目设置
|
||||||
|
|
||||||
@@ -87,6 +97,10 @@ $ yarn build:linux
|
|||||||
|
|
||||||
[](https://star-history.com/#kangfenmao/cherry-studio&Timeline)
|
[](https://star-history.com/#kangfenmao/cherry-studio&Timeline)
|
||||||
|
|
||||||
|
# 社区
|
||||||
|
|
||||||
|
[Telegram](https://t.me/CherryStudioAI)
|
||||||
|
|
||||||
# 赞助
|
# 赞助
|
||||||
|
|
||||||
[微信赞赏码](docs/sponsor.md)
|
[微信赞赏码](docs/sponsor.md)
|
||||||
|
|||||||
@@ -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. 确保测试覆盖主要功能和组件
|
|
||||||
@@ -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. 确保测试覆盖主要功能和组件
|
|
||||||
@@ -9,9 +9,8 @@ files:
|
|||||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||||
- '!src'
|
- '!src'
|
||||||
- '!local'
|
|
||||||
- '!scripts'
|
- '!scripts'
|
||||||
- '!resources'
|
- '!local'
|
||||||
asarUnpack:
|
asarUnpack:
|
||||||
- resources/**
|
- resources/**
|
||||||
win:
|
win:
|
||||||
@@ -64,16 +63,6 @@ electronDownload:
|
|||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
本次更新:
|
修复滚动条显示问题
|
||||||
增加 WebDAV 备份功能 by @DrayChou
|
增加数学公式渲染引擎切换
|
||||||
增加使用 Markdown 渲染用户消息开关
|
修复添加默认助手会添加两个
|
||||||
新的助手设置界面
|
|
||||||
增加 Felo 小程序
|
|
||||||
修复输入框文字撤销问题
|
|
||||||
修复上下文数量为0时报错
|
|
||||||
近期更新:
|
|
||||||
全新应用图标
|
|
||||||
Windows 安装程序支持修改安装位置了
|
|
||||||
增加流式输出开关
|
|
||||||
支持关闭自动检测更新
|
|
||||||
支持消息的编辑
|
|
||||||
|
|||||||
17
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "0.7.10",
|
"version": "0.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
@@ -8,7 +8,11 @@
|
|||||||
"homepage": "https://github.com/kangfenmao/cherry-studio",
|
"homepage": "https://github.com/kangfenmao/cherry-studio",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"local"
|
"local",
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"nohoist": [
|
||||||
|
"packages/database"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -27,6 +31,8 @@
|
|||||||
"build:linux": "dotenv electron-vite build && electron-builder --linux --publish never",
|
"build:linux": "dotenv electron-vite build && electron-builder --linux --publish never",
|
||||||
"release": "node scripts/version.js",
|
"release": "node scripts/version.js",
|
||||||
"publish": "yarn release patch push",
|
"publish": "yarn release patch push",
|
||||||
|
"pulish:artifacts": "cd packages/artifacts && npm publish && cd -",
|
||||||
|
"generate:agents": "yarn workspace @cherry-studio/database agents",
|
||||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build"
|
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -35,10 +41,11 @@
|
|||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"electron-log": "^5.1.5",
|
"electron-log": "^5.1.5",
|
||||||
"electron-store": "^8.2.0",
|
"electron-store": "^8.2.0",
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.3.9",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
|
"officeparser": "^4.1.1",
|
||||||
"unzipper": "^0.12.3",
|
"unzipper": "^0.12.3",
|
||||||
"webdav": "4.11.4"
|
"webdav": "4.11.4"
|
||||||
},
|
},
|
||||||
@@ -96,7 +103,9 @@
|
|||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"rehype-katex": "^7.0.0",
|
"rehype-katex": "^7.0.1",
|
||||||
|
"rehype-mathjax": "^6.0.0",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"sass": "^1.77.2",
|
"sass": "^1.77.2",
|
||||||
|
|||||||
1
packages/artifacts/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Cherry Studio Artifacts
|
||||||
19
packages/artifacts/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@cherry-studio/artifacts",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Cherry Studio Artifacts",
|
||||||
|
"main": "index.js",
|
||||||
|
"homepage": "https://github.com/kangfenmao/cherry-studio/blob/main/npm/artifacts",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"artifacts"
|
||||||
|
],
|
||||||
|
"author": "kangfenmao",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
108
packages/artifacts/statics/word-explanation-card.css
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
:root {
|
||||||
|
/* 莫兰迪色系:使用柔和、低饱和度的颜色 */
|
||||||
|
--primary-color: #b6b5a7; /* 莫兰迪灰褐色,用于背景文字 */
|
||||||
|
--secondary-color: #9a8f8f; /* 莫兰迪灰棕色,用于标题背景 */
|
||||||
|
--accent-color: #c5b4a0; /* 莫兰迪淡棕色,用于强调元素 */
|
||||||
|
--background-color: #e8e3de; /* 莫兰迪米色,用于页面背景 */
|
||||||
|
--text-color: #5b5b5b; /* 莫兰迪深灰色,用于主要文字 */
|
||||||
|
--light-text-color: #8c8c8c; /* 莫兰迪中灰色,用于次要文字 */
|
||||||
|
--divider-color: #d1cbc3; /* 莫兰迪浅灰色,用于分隔线 */
|
||||||
|
}
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--background-color); /* 使用莫兰迪米色作为页面背景 */
|
||||||
|
font-family: 'Noto Sans SC', sans-serif;
|
||||||
|
color: var(--text-color); /* 使用莫兰迪深灰色作为主要文字颜色 */
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: 300px;
|
||||||
|
height: 500px;
|
||||||
|
background-color: #f2ede9; /* 莫兰迪浅米色,用于卡片背景 */
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: var(--secondary-color); /* 使用莫兰迪灰棕色作为标题背景 */
|
||||||
|
color: #f2ede9; /* 浅色文字与深色背景形成对比 */
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-family: 'Noto Serif SC', serif;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.word {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.word-main {
|
||||||
|
font-family: 'Noto Serif SC', serif;
|
||||||
|
font-size: 36px;
|
||||||
|
color: var(--text-color); /* 使用莫兰迪深灰色作为主要词汇颜色 */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.word-main::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -5px;
|
||||||
|
width: 50px;
|
||||||
|
height: 3px;
|
||||||
|
background-color: var(--accent-color); /* 使用莫兰迪淡棕色作为下划线 */
|
||||||
|
}
|
||||||
|
.word-sub {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--light-text-color); /* 使用莫兰迪中灰色作为次要文字颜色 */
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--divider-color); /* 使用莫兰迪浅灰色作为分隔线 */
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.explanation {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-align: left;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.quote {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20px;
|
||||||
|
border-left: 3px solid var(--accent-color); /* 使用莫兰迪淡棕色作为引用边框 */
|
||||||
|
}
|
||||||
|
.background-text {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 150px;
|
||||||
|
color: rgba(182, 181, 167, 0.15); /* 使用莫兰迪灰褐色的透明版本作为背景文字 */
|
||||||
|
z-index: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
3
packages/database/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
data/*
|
||||||
|
!data/.gitkeep
|
||||||
|
|
||||||
BIN
packages/database/.yarn/install-state.gz
Normal file
3
packages/database/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Cherry Studio Database
|
||||||
|
|
||||||
|
Cherry Studio 依赖的数据文件由这个数据库来生成,数据库文件请联系开发者获取
|
||||||
0
packages/database/data/.gitkeep
Normal file
13
packages/database/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@cherry-studio/database",
|
||||||
|
"packageManager": "yarn@4.3.1",
|
||||||
|
"dependencies": {
|
||||||
|
"csv-parser": "^3.0.0",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"agents": "node src/agents.js",
|
||||||
|
"email": "yarn csv && node src/email.js",
|
||||||
|
"csv": "node src/csv.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
45
packages/database/src/agents.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const sqlite3 = require('sqlite3').verbose()
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
// 连接到数据库
|
||||||
|
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error connecting to the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('Connected to the database.')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询数据并转换为JSON
|
||||||
|
db.all('SELECT * FROM agents', [], (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error querying the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 ID 类型转换为字符串
|
||||||
|
for (const row of rows) {
|
||||||
|
row.id = row.id.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将查询结果转换为JSON字符串
|
||||||
|
const jsonData = JSON.stringify(rows, null, 2)
|
||||||
|
|
||||||
|
// 将JSON数据写入文件
|
||||||
|
fs.writeFile('../../src/renderer/src/config/agents.json', jsonData, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error writing to file:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('Data has been written to agents.json')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('Database connection closed.')
|
||||||
|
})
|
||||||
|
})
|
||||||
77
packages/database/src/csv.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const csv = require('csv-parser')
|
||||||
|
const sqlite3 = require('sqlite3').verbose()
|
||||||
|
|
||||||
|
// 连接到 SQLite 数据库
|
||||||
|
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error opening database', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('Connected to the SQLite database.')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建一个数组来存储 CSV 数据
|
||||||
|
const results = []
|
||||||
|
|
||||||
|
// 读取 CSV 文件
|
||||||
|
fs.createReadStream('./data/data.csv')
|
||||||
|
.pipe(csv())
|
||||||
|
.on('data', (data) => results.push(data))
|
||||||
|
.on('end', () => {
|
||||||
|
// 准备 SQL 插入语句,使用 INSERT OR IGNORE
|
||||||
|
const stmt = db.prepare('INSERT OR IGNORE INTO emails (email, github, sent) VALUES (?, ?, ?)')
|
||||||
|
|
||||||
|
// 插入每一行数据
|
||||||
|
let inserted = 0
|
||||||
|
let skipped = 0
|
||||||
|
let emptyEmail = 0
|
||||||
|
|
||||||
|
db.serialize(() => {
|
||||||
|
// 开始一个事务以提高性能
|
||||||
|
db.run('BEGIN TRANSACTION')
|
||||||
|
|
||||||
|
results.forEach((row) => {
|
||||||
|
// 检查 email 是否为空
|
||||||
|
if (!row.email || row.email.trim() === '') {
|
||||||
|
emptyEmail++
|
||||||
|
return // 跳过这一行
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.run(row.email, row['user-href'], 0, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error inserting row', err)
|
||||||
|
} else {
|
||||||
|
if (this.changes === 1) {
|
||||||
|
inserted++
|
||||||
|
} else {
|
||||||
|
skipped++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
db.run('COMMIT', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error committing transaction', err)
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Insertion complete. Inserted: ${inserted}, Skipped (duplicate): ${skipped}, Skipped (empty email): ${emptyEmail}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成插入
|
||||||
|
stmt.finalize()
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing database', err)
|
||||||
|
} else {
|
||||||
|
console.log('Database connection closed.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
36
packages/database/src/email.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const sqlite3 = require('sqlite3').verbose()
|
||||||
|
|
||||||
|
// 连接到数据库
|
||||||
|
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error connecting to the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询数据并转换为JSON
|
||||||
|
db.all('SELECT * FROM emails WHERE sent = 0', [], (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error querying the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
console.log(row.email)
|
||||||
|
// Update row set sent = 1
|
||||||
|
db.run('UPDATE emails SET sent = 1 WHERE id = ?', [row.id], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error updating the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
db.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing the database:', err.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
1643
packages/database/yarn.lock
Normal file
118
resources/cherry-studio/license.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>CherryStudio 许可协议-ZH/EN</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100 p-8">
|
||||||
|
<div class="container mx-auto bg-white p-6 rounded shadow-lg">
|
||||||
|
<h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio 许可协议</h1>
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-2xl font-semibold mb-4">许可协议</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
本软件采用 <strong>Apache License 2.0</strong> 许可。除 Apache License 2.0 规定的条款外,您在使用 Cherry
|
||||||
|
Studio 时还应遵守以下附加条款:
|
||||||
|
</p>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">一. 商用许可</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li><strong>免费商用</strong>:用户在不修改代码的情况下,可以免费用于商业目的。</li>
|
||||||
|
<li>
|
||||||
|
<strong>商业授权</strong>:如果您满足以下任意条件之一,需取得商业授权:
|
||||||
|
<ol class="list-decimal list-inside ml-4">
|
||||||
|
<li>对本软件进行二次修改、开发(包括但不限于修改应用名称、logo、代码以及功能)。</li>
|
||||||
|
<li>为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。</li>
|
||||||
|
<li>预装或集成到硬件设备或产品中进行捆绑销售。</li>
|
||||||
|
<li>政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">二. 贡献者协议</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li><strong>许可调整</strong>:生产者有权根据需要对开源协议进行调整,使其更加严格或宽松。</li>
|
||||||
|
<li><strong>商业用途</strong>:您贡献的代码可能会被用于商业用途,包括但不限于云业务运营。</li>
|
||||||
|
</ol>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">三. 其他条款</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li>本协议条款的解释权归 Cherry Studio 开发者所有。</li>
|
||||||
|
<li>本协议可能根据实际情况进行更新,更新时将通过本软件通知用户。</li>
|
||||||
|
</ol>
|
||||||
|
<p class="mb-4">如有任何问题或需申请商业授权,请联系 Cherry Studio 开发团队。</p>
|
||||||
|
<p>
|
||||||
|
除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。有关 Apache License 2.0 的详细信息,请访问
|
||||||
|
<a href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
|
class="text-blue-500 underline">http://www.apache.org/licenses/LICENSE-2.0</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold mb-6 text-center">Cherry Studio License</h1>
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-2xl font-semibold mb-4">License Agreement</h2>
|
||||||
|
<p class="mb-4">
|
||||||
|
This software is licensed under the <strong>Apache License 2.0</strong>. In addition to the terms of the
|
||||||
|
Apache License 2.0, the following additional terms apply to the use of Cherry Studio:
|
||||||
|
</p>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">I. Commercial Use License</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li>
|
||||||
|
<strong>Free Commercial Use</strong>: Users can use the software for commercial purposes without
|
||||||
|
modifying
|
||||||
|
the code.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Commercial License Required</strong>: A commercial license is required if any of the
|
||||||
|
following
|
||||||
|
conditions are met:
|
||||||
|
<ol class="list-decimal list-inside ml-4">
|
||||||
|
<li>
|
||||||
|
You modify, develop, or alter the software, including but not limited to changes to the
|
||||||
|
application
|
||||||
|
name, logo, code, or functionality.
|
||||||
|
</li>
|
||||||
|
<li>You provide multi-tenant services to enterprise customers with 10 or more users.</li>
|
||||||
|
<li>
|
||||||
|
You pre-install or integrate the software into hardware devices or products and bundle it
|
||||||
|
for sale.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
You are engaging in large-scale procurement for government or educational institutions,
|
||||||
|
especially
|
||||||
|
involving security, data privacy, or other sensitive requirements.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">II. Contributor Agreement</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li>
|
||||||
|
<strong>License Adjustment</strong>: The producer reserves the right to adjust the open-source
|
||||||
|
license as
|
||||||
|
needed, making it stricter or more lenient.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Commercial Use</strong>: Any code you contribute may be used for commercial purposes,
|
||||||
|
including but
|
||||||
|
not limited to cloud business operations.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">III. Other Terms</h3>
|
||||||
|
<ol class="list-decimal list-inside mb-4">
|
||||||
|
<li>The interpretation of these terms is subject to the discretion of Cherry Studio developers.</li>
|
||||||
|
<li>These terms may be updated, and users will be notified through the software when changes occur.</li>
|
||||||
|
</ol>
|
||||||
|
<p class="mb-4">
|
||||||
|
For any questions or to request a commercial license, please contact the Cherry Studio development team.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache
|
||||||
|
License 2.0. Detailed information about the Apache License 2.0 can be found at
|
||||||
|
<a href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
|
class="text-blue-500 underline">http://www.apache.org/licenses/LICENSE-2.0</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
91
src/main/constant.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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', // 环境变量文件
|
||||||
|
'.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 代码文件
|
||||||
|
'.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 构建文件
|
||||||
|
'.kts', // Kotlin Script 文件
|
||||||
|
'.java' // Java 代码文件
|
||||||
|
]
|
||||||
@@ -4,6 +4,7 @@ import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config'
|
|||||||
import AppUpdater from './services/AppUpdater'
|
import AppUpdater from './services/AppUpdater'
|
||||||
import BackupManager from './services/BackupManager'
|
import BackupManager from './services/BackupManager'
|
||||||
import FileManager from './services/FileManager'
|
import FileManager from './services/FileManager'
|
||||||
|
import { compress, decompress } from './utils/zip'
|
||||||
import { createMinappWindow } from './window'
|
import { createMinappWindow } from './window'
|
||||||
|
|
||||||
const fileManager = new FileManager()
|
const fileManager = new FileManager()
|
||||||
@@ -29,6 +30,8 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
|
|
||||||
ipcMain.handle('reload', () => mainWindow.reload())
|
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:backup', backupManager.backup)
|
||||||
ipcMain.handle('backup:restore', backupManager.restore)
|
ipcMain.handle('backup:restore', backupManager.restore)
|
||||||
ipcMain.handle('backup:backupToWebdav', backupManager.backupToWebdav)
|
ipcMain.handle('backup:backupToWebdav', backupManager.backupToWebdav)
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ export default class AppUpdater {
|
|||||||
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
||||||
autoUpdater.logger?.info('检测到新版本,确认是否下载')
|
autoUpdater.logger?.info('检测到新版本,确认是否下载')
|
||||||
mainWindow.webContents.send('update-available', releaseInfo)
|
mainWindow.webContents.send('update-available', releaseInfo)
|
||||||
|
|
||||||
const releaseNotes = releaseInfo.releaseNotes
|
const releaseNotes = releaseInfo.releaseNotes
|
||||||
let releaseContent = ''
|
let releaseContent = ''
|
||||||
|
|
||||||
if (releaseNotes) {
|
if (releaseNotes) {
|
||||||
if (typeof releaseNotes === 'string') {
|
if (typeof releaseNotes === 'string') {
|
||||||
releaseContent = <string>releaseNotes
|
releaseContent = <string>releaseNotes
|
||||||
|
|||||||
@@ -102,7 +102,13 @@ class BackupManager {
|
|||||||
const webdavClient = new WebDav(webdavConfig)
|
const webdavClient = new WebDav(webdavConfig)
|
||||||
const retrievedFile = await webdavClient.getFileContents(filename)
|
const retrievedFile = await webdavClient.getFileContents(filename)
|
||||||
const backupedFilePath = path.join(this.backupDir, filename)
|
const backupedFilePath = path.join(this.backupDir, filename)
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.backupDir)) {
|
||||||
|
fs.mkdirSync(this.backupDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
await fs.writeFileSync(backupedFilePath, retrievedFile as Buffer)
|
await fs.writeFileSync(backupedFilePath, retrievedFile as Buffer)
|
||||||
|
|
||||||
return await this.restore(_, backupedFilePath)
|
return await this.restore(_, backupedFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { documentExts } from '@main/constant'
|
||||||
import { getFileType } from '@main/utils/file'
|
import { getFileType } from '@main/utils/file'
|
||||||
import { FileType } from '@types'
|
import { FileType } from '@types'
|
||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
@@ -13,11 +14,14 @@ import logger from 'electron-log'
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { writeFileSync } from 'fs'
|
import { writeFileSync } from 'fs'
|
||||||
import { readFile } from 'fs/promises'
|
import { readFile } from 'fs/promises'
|
||||||
|
import officeParser from 'officeparser'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import { chdir } from 'process'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
class FileManager {
|
class FileManager {
|
||||||
private storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
private storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
||||||
|
private tempDir = path.join(app.getPath('temp'), 'CherryStudio')
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initStorageDir()
|
this.initStorageDir()
|
||||||
@@ -27,6 +31,9 @@ class FileManager {
|
|||||||
if (!fs.existsSync(this.storageDir)) {
|
if (!fs.existsSync(this.storageDir)) {
|
||||||
fs.mkdirSync(this.storageDir, { recursive: true })
|
fs.mkdirSync(this.storageDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
if (!fs.existsSync(this.tempDir)) {
|
||||||
|
fs.mkdirSync(this.tempDir, { recursive: true })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFileHash = async (filePath: string): Promise<string> => {
|
private getFileHash = async (filePath: string): Promise<string> => {
|
||||||
@@ -173,15 +180,29 @@ class FileManager {
|
|||||||
|
|
||||||
public readFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<string> => {
|
public readFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<string> => {
|
||||||
const filePath = path.join(this.storageDir, id)
|
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')
|
return fs.readFileSync(filePath, 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
public createTempFile = async (_: Electron.IpcMainInvokeEvent, fileName: string): Promise<string> => {
|
public createTempFile = async (_: Electron.IpcMainInvokeEvent, fileName: string): Promise<string> => {
|
||||||
const tempDir = path.join(app.getPath('temp'), 'CherryStudio')
|
if (!fs.existsSync(this.tempDir)) {
|
||||||
if (!fs.existsSync(tempDir)) {
|
fs.mkdirSync(this.tempDir, { recursive: true })
|
||||||
fs.mkdirSync(tempDir, { recursive: true })
|
|
||||||
}
|
}
|
||||||
const tempFilePath = path.join(tempDir, `temp_file_${uuidv4()}_${fileName}`)
|
const tempFilePath = path.join(this.tempDir, `temp_file_${uuidv4()}_${fileName}`)
|
||||||
return tempFilePath
|
return tempFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class WebDav {
|
|||||||
private webdavPath: string
|
private webdavPath: string
|
||||||
|
|
||||||
constructor(params: WebDavConfig) {
|
constructor(params: WebDavConfig) {
|
||||||
this.webdavPath = params.webdavPath.replace('/', '')
|
this.webdavPath = params.webdavPath
|
||||||
|
|
||||||
this.instance = createClient(params.webdavHost, {
|
this.instance = createClient(params.webdavHost, {
|
||||||
username: params.webdavUser,
|
username: params.webdavUser,
|
||||||
|
|||||||
@@ -1,101 +1,8 @@
|
|||||||
|
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@main/constant'
|
||||||
|
|
||||||
import { FileTypes } from '../../renderer/src/types'
|
import { FileTypes } from '../../renderer/src/types'
|
||||||
|
|
||||||
export function getFileType(ext: string): FileTypes {
|
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()
|
ext = ext.toLowerCase()
|
||||||
if (imageExts.includes(ext)) return FileTypes.IMAGE
|
if (imageExts.includes(ext)) return FileTypes.IMAGE
|
||||||
if (videoExts.includes(ext)) return FileTypes.VIDEO
|
if (videoExts.includes(ext)) return FileTypes.VIDEO
|
||||||
|
|||||||
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'
|
const theme = appConfig.get('theme') || 'light'
|
||||||
|
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
@@ -25,11 +27,12 @@ export function createMainWindow() {
|
|||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
show: true,
|
show: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
transparent: process.platform === 'darwin',
|
transparent: isMac,
|
||||||
vibrancy: 'fullscreen-ui',
|
vibrancy: 'fullscreen-ui',
|
||||||
visualEffectState: 'active',
|
visualEffectState: 'active',
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: 'hidden',
|
||||||
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
||||||
|
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
|
||||||
trafficLightPosition: { x: 8, y: 12 },
|
trafficLightPosition: { x: 8, y: 12 },
|
||||||
...(process.platform === 'linux' ? { icon } : {}),
|
...(process.platform === 'linux' ? { icon } : {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@@ -45,11 +48,9 @@ export function createMainWindow() {
|
|||||||
|
|
||||||
mainWindow.webContents.on('context-menu', () => {
|
mainWindow.webContents.on('context-menu', () => {
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
menu.append(new MenuItem({ label: '复制', role: 'copy', sublabel: '⌘ + C' }))
|
menu.append(new MenuItem({ label: '复制', role: 'copy' }))
|
||||||
menu.append(new MenuItem({ label: '粘贴', role: 'paste', sublabel: '⌘ + V' }))
|
menu.append(new MenuItem({ label: '粘贴', role: 'paste' }))
|
||||||
menu.append(new MenuItem({ label: '剪切', role: 'cut', sublabel: '⌘ + X' }))
|
menu.append(new MenuItem({ label: '剪切', role: 'cut' }))
|
||||||
menu.append(new MenuItem({ type: 'separator' }))
|
|
||||||
menu.append(new MenuItem({ label: '全选', role: 'selectAll', sublabel: '⌘ + A' }))
|
|
||||||
menu.popup()
|
menu.popup()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const api = {
|
|||||||
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme),
|
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('set-theme', theme),
|
||||||
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
|
minApp: (url: string) => ipcRenderer.invoke('minapp', url),
|
||||||
reload: () => ipcRenderer.invoke('reload'),
|
reload: () => ipcRenderer.invoke('reload'),
|
||||||
|
compress: (text: string) => ipcRenderer.invoke('zip:compress', text),
|
||||||
|
decompress: (text: Buffer) => ipcRenderer.invoke('zip:decompress', text),
|
||||||
backup: {
|
backup: {
|
||||||
backup: (fileName: string, data: string, destinationPath?: string) =>
|
backup: (fileName: string, data: string, destinationPath?: string) =>
|
||||||
ipcRenderer.invoke('backup:backup', fileName, data, destinationPath),
|
ipcRenderer.invoke('backup:backup', fileName, data, destinationPath),
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<head>
|
||||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
<meta charset="UTF-8" />
|
||||||
<meta
|
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||||
http-equiv="Content-Security-Policy"
|
<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:" />
|
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>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#spinner {
|
|
||||||
position: fixed;
|
#spinner {
|
||||||
width: 100vw;
|
position: fixed;
|
||||||
height: 100vh;
|
width: 100vw;
|
||||||
display: flex;
|
height: 100vh;
|
||||||
flex-direction: row;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: row;
|
||||||
align-items: center;
|
justify-content: center;
|
||||||
background: rgba(255, 255, 255, 0.5);
|
align-items: center;
|
||||||
}
|
}
|
||||||
#spinner img {
|
|
||||||
width: 100px;
|
#spinner img {
|
||||||
}
|
width: 100px;
|
||||||
</style>
|
border-radius: 50px;
|
||||||
</head>
|
}
|
||||||
<body>
|
</style>
|
||||||
<div id="root"></div>
|
</head>
|
||||||
<div id="spinner">
|
|
||||||
<img src="/src/assets/images/logo/cherry-text.svg" />
|
<body>
|
||||||
</div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<div id="spinner">
|
||||||
</body>
|
<img src="/src/assets/images/logo.png" />
|
||||||
</html>
|
</div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -12,6 +12,7 @@ import { ThemeProvider } from './context/ThemeProvider'
|
|||||||
import AgentsPage from './pages/agents/AgentsPage'
|
import AgentsPage from './pages/agents/AgentsPage'
|
||||||
import AppsPage from './pages/apps/AppsPage'
|
import AppsPage from './pages/apps/AppsPage'
|
||||||
import FilesPage from './pages/files/FilesPage'
|
import FilesPage from './pages/files/FilesPage'
|
||||||
|
import HistoryPage from './pages/history/HistoryPage'
|
||||||
import HomePage from './pages/home/HomePage'
|
import HomePage from './pages/home/HomePage'
|
||||||
import SettingsPage from './pages/settings/SettingsPage'
|
import SettingsPage from './pages/settings/SettingsPage'
|
||||||
import TranslatePage from './pages/translate/TranslatePage'
|
import TranslatePage from './pages/translate/TranslatePage'
|
||||||
@@ -31,6 +32,7 @@ function App(): JSX.Element {
|
|||||||
<Route path="/agents" element={<AgentsPage />} />
|
<Route path="/agents" element={<AgentsPage />} />
|
||||||
<Route path="/translate" element={<TranslatePage />} />
|
<Route path="/translate" element={<TranslatePage />} />
|
||||||
<Route path="/apps" element={<AppsPage />} />
|
<Route path="/apps" element={<AppsPage />} />
|
||||||
|
<Route path="/messages/*" element={<HistoryPage />} />
|
||||||
<Route path="/settings/*" element={<SettingsPage />} />
|
<Route path="/settings/*" element={<SettingsPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
|||||||
18
src/renderer/src/assets/images/apps/bolt.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="16" height="16" rx="4" fill="black"/>
|
||||||
|
<g filter="url(#filter0_i_2119_154)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64368 11.7731C7.91976 11.7731 7.20901 11.5147 6.80099 10.9591L6.65707 11.6143L4 13L4.28684 11.6143L6.22186 3H8.59103L7.9066 6.03634C8.45941 5.44199 8.97273 5.22234 9.63083 5.22234C11.0523 5.22234 12 6.1397 12 7.81938C12 9.55074 10.9076 11.7731 8.64368 11.7731ZM9.55186 8.31036C9.55186 9.11144 8.97273 9.71871 8.22249 9.71871C7.8013 9.71871 7.4196 9.56366 7.16952 9.29233L7.53806 7.70309C7.81447 7.43176 8.13036 7.27671 8.49889 7.27671C9.06486 7.27671 9.55186 7.69017 9.55186 8.31036Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_2119_154" x="4" y="3" width="8" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feGaussianBlur stdDeviation="0.0192413"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.95 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2119_154"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/renderer/src/assets/images/models/adept.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/renderer/src/assets/images/models/adept_dark.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/renderer/src/assets/images/models/aisingapore.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/renderer/src/assets/images/models/aisingapore_dark.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/renderer/src/assets/images/models/bigcode.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
src/renderer/src/assets/images/models/bigcode_dark.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/renderer/src/assets/images/models/dianxin.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/renderer/src/assets/images/models/dianxin_dark.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/renderer/src/assets/images/models/google.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/renderer/src/assets/images/models/huggingface.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/renderer/src/assets/images/models/huggingface_dark.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/renderer/src/assets/images/models/ibm.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/renderer/src/assets/images/models/ibm_dark.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/renderer/src/assets/images/models/mediatek.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/renderer/src/assets/images/models/mediatek_dark.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/renderer/src/assets/images/models/nvidia.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/renderer/src/assets/images/models/nvidia_dark.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
src/renderer/src/assets/images/models/rakutenai.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/renderer/src/assets/images/models/rakutenai_dark.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/renderer/src/assets/images/models/tele.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/renderer/src/assets/images/models/tele_dark.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src/renderer/src/assets/images/models/upstage.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/renderer/src/assets/images/models/upstage_dark.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/renderer/src/assets/images/providers/bailian.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/renderer/src/assets/images/providers/fireworks.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/renderer/src/assets/images/providers/nvidia.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -2,10 +2,8 @@
|
|||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-nav-dropdown {
|
.ant-btn:not(:disabled):focus-visible {
|
||||||
.ant-dropdown-menu {
|
outline: none;
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-segmented-group {
|
.ant-segmented-group {
|
||||||
@@ -41,6 +39,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.segmented-tab {
|
.segmented-tab {
|
||||||
|
.ant-segmented-item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.ant-segmented-item-selected {
|
.ant-segmented-item-selected {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import './markdown.scss';
|
@import './markdown.scss';
|
||||||
@import './scrollbar.scss';
|
|
||||||
@import './ant.scss';
|
@import './ant.scss';
|
||||||
|
@import './scrollbar.scss';
|
||||||
@import '../fonts/icon-fonts/iconfont.css';
|
@import '../fonts/icon-fonts/iconfont.css';
|
||||||
@import '../fonts/ubuntu/ubuntu.css';
|
@import '../fonts/ubuntu/ubuntu.css';
|
||||||
|
|
||||||
@@ -37,8 +37,6 @@
|
|||||||
--color-error: #f44336;
|
--color-error: #f44336;
|
||||||
--color-link: #1677ff;
|
--color-link: #1677ff;
|
||||||
--color-code-background: #323232;
|
--color-code-background: #323232;
|
||||||
--color-scrollbar-thumb: rgba(255, 255, 255, 0.08);
|
|
||||||
--color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.15);
|
|
||||||
--color-hover: rgba(40, 40, 40, 1);
|
--color-hover: rgba(40, 40, 40, 1);
|
||||||
--color-active: rgba(55, 55, 55, 1);
|
--color-active: rgba(55, 55, 55, 1);
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@
|
|||||||
--navbar-height: 40px;
|
--navbar-height: 40px;
|
||||||
--sidebar-width: 50px;
|
--sidebar-width: 50px;
|
||||||
--status-bar-height: 40px;
|
--status-bar-height: 40px;
|
||||||
--input-bar-height: 85px;
|
--input-bar-height: 100px;
|
||||||
|
|
||||||
--assistants-width: 275px;
|
--assistants-width: 275px;
|
||||||
--topic-list-width: 275px;
|
--topic-list-width: 275px;
|
||||||
@@ -88,8 +86,6 @@ body[theme-mode='light'] {
|
|||||||
--color-error: #f44336;
|
--color-error: #f44336;
|
||||||
--color-link: #1677ff;
|
--color-link: #1677ff;
|
||||||
--color-code-background: #e3e3e3;
|
--color-code-background: #e3e3e3;
|
||||||
--color-scrollbar-thumb: rgba(0, 0, 0, 0.08);
|
|
||||||
--color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.15);
|
|
||||||
--color-hover: var(--color-white-mute);
|
--color-hover: var(--color-white-mute);
|
||||||
--color-active: var(--color-white-soft);
|
--color-active: var(--color-white-soft);
|
||||||
|
|
||||||
@@ -140,6 +136,7 @@ html,
|
|||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +157,6 @@ body[os='mac'] {
|
|||||||
#content-container {
|
#content-container {
|
||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
border-bottom-left-radius: 10px;
|
border-bottom-left-radius: 10px;
|
||||||
border-top-right-radius: 10px;
|
|
||||||
border-left: 0.5px solid var(--color-border);
|
border-left: 0.5px solid var(--color-border);
|
||||||
box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@@ -97,7 +99,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
white-space: pre-wrap !important;
|
|
||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +110,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap !important;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: 'Fira Code', 'Courier New', Courier, monospace;
|
font-family: 'Fira Code', 'Courier New', Courier, monospace;
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
|
:root {
|
||||||
|
--color-scrollbar-thumb: rgba(255, 255, 255, 0.15);
|
||||||
|
--color-scrollbar-thumb-hover: rgba(255, 255, 255, 0.2);
|
||||||
|
--color-scrollbar-thumb-right: rgba(255, 255, 255, 0.25);
|
||||||
|
--color-scrollbar-thumb-right-hover: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme-mode='light'] {
|
||||||
|
--color-scrollbar-thumb: rgba(0, 0, 0, 0.15);
|
||||||
|
--color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.2);
|
||||||
|
--color-scrollbar-thumb-right: rgba(0, 0, 0, 0.25);
|
||||||
|
--color-scrollbar-thumb-right-hover: rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
/* 全局初始化滚动条样式 */
|
/* 全局初始化滚动条样式 */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 2px;
|
width: 5px;
|
||||||
height: 2px;
|
height: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@@ -9,8 +23,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 10px;
|
||||||
background: var(--color-scrollbar-thumb);
|
background: var(--color-scrollbar-thumb);
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-scrollbar-thumb-hover);
|
background: var(--color-scrollbar-thumb-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
|
import { Assistant, AssistantMessage, AssistantSettings } from '@renderer/types'
|
||||||
|
import { Button, Card, Col, Divider, Form as FormAntd, FormInstance, Row, Space, Switch } from 'antd'
|
||||||
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import { FC, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
assistant: Assistant
|
||||||
|
updateAssistant: (assistant: Assistant) => void
|
||||||
|
updateAssistantSettings: (settings: Partial<AssistantSettings>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssistantMessagesSettings: FC<Props> = ({ assistant, updateAssistant, updateAssistantSettings }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [form] = Form.useForm()
|
||||||
|
const formRef = useRef<FormInstance>(null)
|
||||||
|
const [messages, setMessagess] = useState<AssistantMessage[]>(assistant?.messages || [])
|
||||||
|
const [hideMessages, setHideMessages] = useState(assistant?.settings?.hideMessages || false)
|
||||||
|
|
||||||
|
const onSave = () => {
|
||||||
|
// 检查是否有空对话组
|
||||||
|
for (let i = 0; i < messages.length; i += 2) {
|
||||||
|
const userContent = messages[i].content.trim()
|
||||||
|
const assistantContent = messages[i + 1]?.content.trim()
|
||||||
|
if (userContent === '' || assistantContent === '') {
|
||||||
|
window.modal.error({
|
||||||
|
centered: true,
|
||||||
|
content: t('agents.edit.message.empty.content')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉空消息并将消息分组
|
||||||
|
const filteredMessagess = messages.reduce((acc, conv, index) => {
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
const userContent = conv.content.trim()
|
||||||
|
const assistantContent = messages[index + 1]?.content.trim()
|
||||||
|
if (userContent !== '' || assistantContent !== '') {
|
||||||
|
acc.push({ role: 'user', content: userContent }, { role: 'assistant', content: assistantContent })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [] as AssistantMessage[])
|
||||||
|
|
||||||
|
updateAssistant({
|
||||||
|
...assistant,
|
||||||
|
messages: filteredMessagess
|
||||||
|
})
|
||||||
|
|
||||||
|
window.message.success({ content: t('message.save.success.title'), key: 'save-messages' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMessages = () => {
|
||||||
|
setMessagess([...messages, { role: 'user', content: '' }, { role: 'assistant', content: '' }])
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMessages = (index: number, role: 'user' | 'assistant', content: string) => {
|
||||||
|
const newMessagess = [...messages]
|
||||||
|
newMessagess[index] = { role, content }
|
||||||
|
setMessagess(newMessagess)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteMessages = (index: number) => {
|
||||||
|
const newMessagess = [...messages]
|
||||||
|
newMessagess.splice(index, 2) // 删除用户和助手的对话
|
||||||
|
setMessagess(newMessagess)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Form ref={formRef} layout="vertical" form={form} labelAlign="right" colon={false}>
|
||||||
|
<Form.Item label={t('agents.edit.settings.hide_preset_messages')}>
|
||||||
|
<Switch
|
||||||
|
checked={hideMessages}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setHideMessages(checked)
|
||||||
|
updateAssistantSettings({ hideMessages: checked })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Divider style={{ marginBottom: 15 }} />
|
||||||
|
<Form.Item label={t('agents.edit.message.group.title')}>
|
||||||
|
{messages.map(
|
||||||
|
(_, index) =>
|
||||||
|
index % 2 === 0 && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
key={index}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
title={`${t('agents.edit.message.group.title')} #${index / 2 + 1}`}
|
||||||
|
extra={<Button icon={<DeleteOutlined />} type="text" danger onClick={() => deleteMessages(index)} />}>
|
||||||
|
<Row gutter={16} align="middle" style={{ marginBottom: 16 }}>
|
||||||
|
<Col span={3}>
|
||||||
|
<label>{t('agents.edit.message.user.title')}</label>
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<TextArea
|
||||||
|
value={messages[index].content}
|
||||||
|
onChange={(e) => updateMessages(index, 'user', e.target.value)}
|
||||||
|
placeholder={t('agents.edit.message.user.placeholder')}
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16} align="top">
|
||||||
|
<Col span={3}>
|
||||||
|
<label>{t('agents.edit.message.assistant.title')}</label>
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<TextArea
|
||||||
|
value={messages[index + 1]?.content || ''}
|
||||||
|
onChange={(e) => updateMessages(index + 1, 'assistant', e.target.value)}
|
||||||
|
placeholder={t('agents.edit.message.assistant.placeholder')}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<Space>
|
||||||
|
<Button icon={<PlusOutlined />} onClick={addMessages}>
|
||||||
|
{t('agents.edit.message.add.title')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
<Divider style={{ marginBottom: 15 }} />
|
||||||
|
<Form.Item>
|
||||||
|
{messages.length > 0 && (
|
||||||
|
<Button type="primary" onClick={onSave}>
|
||||||
|
{t('common.save')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<div style={{ minHeight: 50 }} />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding-top: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Form = styled(FormAntd)`
|
||||||
|
.ant-form-item-no-colon {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default AssistantMessagesSettings
|
||||||
@@ -1,81 +1,107 @@
|
|||||||
import { QuestionCircleOutlined } from '@ant-design/icons'
|
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { DEFAULT_CONEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { SettingRow } from '@renderer/pages/settings'
|
||||||
import { SettingRow, SettingRowTitle } from '@renderer/pages/settings'
|
|
||||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||||
import { Button, Col, Row, Slider, Switch, Tooltip } from 'antd'
|
import { Button, Col, Divider, Row, Slider, Switch, Tooltip } from 'antd'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import ModelAvatar from '../Avatar/ModelAvatar'
|
||||||
|
import SelectModelPopup from '../Popups/SelectModelPopup'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
|
updateAssistant: (assistant: Assistant) => void
|
||||||
|
updateAssistantSettings: (settings: Partial<AssistantSettings>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssistantModelSettings: FC<Props> = (props) => {
|
const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateAssistantSettings }) => {
|
||||||
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
|
|
||||||
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
||||||
const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
||||||
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
||||||
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
||||||
|
const [autoResetModel, setAutoResetModel] = useState(assistant?.settings?.autoResetModel ?? false)
|
||||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||||
|
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
|
||||||
updateAssistantSettings({
|
|
||||||
temperature: settings.temperature ?? temperature,
|
|
||||||
contextCount: settings.contextCount ?? contextCount,
|
|
||||||
enableMaxTokens: settings.enableMaxTokens ?? enableMaxTokens,
|
|
||||||
maxTokens: settings.maxTokens ?? maxTokens,
|
|
||||||
streamOutput: settings.streamOutput ?? streamOutput
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTemperatureChange = (value) => {
|
const onTemperatureChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
onUpdateAssistantSettings({ temperature: value })
|
updateAssistantSettings({ temperature: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onConextCountChange = (value) => {
|
const onConextCountChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
onUpdateAssistantSettings({ contextCount: value })
|
updateAssistantSettings({ contextCount: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMaxTokensChange = (value) => {
|
const onMaxTokensChange = (value) => {
|
||||||
if (!isNaN(value as number)) {
|
if (!isNaN(value as number)) {
|
||||||
onUpdateAssistantSettings({ maxTokens: value })
|
updateAssistantSettings({ maxTokens: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setTemperature(DEFAULT_TEMPERATURE)
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
setConextCount(DEFAULT_CONEXTCOUNT)
|
setConextCount(DEFAULT_CONEXTCOUNT)
|
||||||
updateAssistant({
|
setEnableMaxTokens(false)
|
||||||
...assistant,
|
setMaxTokens(0)
|
||||||
settings: {
|
setStreamOutput(true)
|
||||||
...assistant.settings,
|
updateAssistantSettings({
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
contextCount: DEFAULT_CONEXTCOUNT,
|
contextCount: DEFAULT_CONEXTCOUNT,
|
||||||
enableMaxTokens: false,
|
enableMaxTokens: false,
|
||||||
maxTokens: DEFAULT_MAX_TOKENS,
|
maxTokens: 0,
|
||||||
streamOutput: true
|
streamOutput: true
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const onSelectModel = async () => {
|
||||||
setTemperature(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
const selectedModel = await SelectModelPopup.show({ model: assistant?.model })
|
||||||
setConextCount(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
|
if (selectedModel) {
|
||||||
setEnableMaxTokens(assistant?.settings?.enableMaxTokens ?? false)
|
setDefaultModel(selectedModel)
|
||||||
setMaxTokens(assistant?.settings?.maxTokens ?? DEFAULT_MAX_TOKENS)
|
updateAssistant({
|
||||||
setStreamOutput(assistant?.settings?.streamOutput ?? true)
|
...assistant,
|
||||||
}, [assistant])
|
defaultModel: selectedModel
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
<Row align="middle" style={{ marginBottom: 10 }}>
|
||||||
|
<Label style={{ marginBottom: 10 }}>{t('assistants.settings.default_model')}</Label>
|
||||||
|
<Col span={24}>
|
||||||
|
<HStack alignItems="center">
|
||||||
|
<Button
|
||||||
|
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusOutlined />}
|
||||||
|
onClick={onSelectModel}>
|
||||||
|
{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
|
<SettingRow style={{ minHeight: 30 }}>
|
||||||
|
<Label>
|
||||||
|
{t('assistants.settings.auto_reset_model')}{' '}
|
||||||
|
<Tooltip title={t('assistants.settings.auto_reset_model.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
value={autoResetModel}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setAutoResetModel(checked)
|
||||||
|
updateAssistantSettings({ autoResetModel: checked })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
<Row align="middle">
|
<Row align="middle">
|
||||||
<Label>{t('chat.settings.temperature')}</Label>
|
<Label>{t('chat.settings.temperature')}</Label>
|
||||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||||
@@ -95,10 +121,12 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="middle">
|
<Row align="middle">
|
||||||
<Label>{t('chat.settings.conext_count')}</Label>
|
<Label>
|
||||||
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
{t('chat.settings.conext_count')}{' '}
|
||||||
<QuestionIcon />
|
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
||||||
</Tooltip>
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Label>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="middle" gutter={10}>
|
<Row align="middle" gutter={10}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
@@ -123,7 +151,7 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
|||||||
checked={enableMaxTokens}
|
checked={enableMaxTokens}
|
||||||
onChange={(enabled) => {
|
onChange={(enabled) => {
|
||||||
setEnableMaxTokens(enabled)
|
setEnableMaxTokens(enabled)
|
||||||
onUpdateAssistantSettings({ enableMaxTokens: enabled })
|
updateAssistantSettings({ enableMaxTokens: enabled })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -141,18 +169,17 @@ const AssistantModelSettings: FC<Props> = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('model.stream_output')}</SettingRowTitleSmall>
|
<Label>{t('model.stream_output')}</Label>
|
||||||
<Switch
|
<Switch
|
||||||
checked={streamOutput}
|
checked={streamOutput}
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
setStreamOutput(checked)
|
setStreamOutput(checked)
|
||||||
onUpdateAssistantSettings({ streamOutput: checked })
|
updateAssistantSettings({ streamOutput: checked })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<HStack
|
<Divider style={{ margin: '15px 0' }} />
|
||||||
justifyContent="flex-end"
|
<HStack justifyContent="flex-end">
|
||||||
style={{ marginTop: 20, padding: '10px 0', borderTop: '0.5px solid var(--color-border)' }}>
|
|
||||||
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
|
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
|
||||||
{t('chat.settings.reset')}
|
{t('chat.settings.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -166,12 +193,12 @@ const Container = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: 10px;
|
padding: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Label = styled.p`
|
const Label = styled.p`
|
||||||
margin: 0;
|
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
font-weight: 500;
|
||||||
`
|
`
|
||||||
|
|
||||||
const QuestionIcon = styled(QuestionCircleOutlined)`
|
const QuestionIcon = styled(QuestionCircleOutlined)`
|
||||||
@@ -180,8 +207,4 @@ const QuestionIcon = styled(QuestionCircleOutlined)`
|
|||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
`
|
`
|
||||||
|
|
||||||
const SettingRowTitleSmall = styled(SettingRowTitle)`
|
|
||||||
font-size: 13px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default AssistantModelSettings
|
export default AssistantModelSettings
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||||
import { syncAsistantToAgent } from '@renderer/services/assistant'
|
import { Button, Input } from 'antd'
|
||||||
import { Assistant } from '@renderer/types'
|
|
||||||
import { Input } from 'antd'
|
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { Box, VStack } from '../Layout'
|
import { Box, HStack } from '../Layout'
|
||||||
|
|
||||||
const AssistantPromptSettings: React.FC<{ assistant: Assistant }> = (props) => {
|
interface Props {
|
||||||
const { assistant, updateAssistant } = useAssistant(props.assistant.id)
|
assistant: Assistant
|
||||||
|
updateAssistant: (assistant: Assistant) => void
|
||||||
|
updateAssistantSettings: (settings: AssistantSettings) => void
|
||||||
|
onOk: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant, onOk }) => {
|
||||||
const [name, setName] = useState(assistant.name)
|
const [name, setName] = useState(assistant.name)
|
||||||
const [prompt, setPrompt] = useState(assistant.prompt)
|
const [prompt, setPrompt] = useState(assistant.prompt)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -17,19 +22,20 @@ const AssistantPromptSettings: React.FC<{ assistant: Assistant }> = (props) => {
|
|||||||
const onUpdate = () => {
|
const onUpdate = () => {
|
||||||
const _assistant = { ...assistant, name, prompt }
|
const _assistant = { ...assistant, name, prompt }
|
||||||
updateAssistant(_assistant)
|
updateAssistant(_assistant)
|
||||||
syncAsistantToAgent(_assistant)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack flex={1}>
|
<Container>
|
||||||
<Box mb={8}>{t('common.name')}</Box>
|
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
|
{t('common.name')}
|
||||||
|
</Box>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('common.assistant') + t('common.name')}
|
placeholder={t('common.assistant') + t('common.name')}
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
onBlur={onUpdate}
|
onBlur={onUpdate}
|
||||||
/>
|
/>
|
||||||
<Box mt={8} mb={8}>
|
<Box mt={8} mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
{t('common.prompt')}
|
{t('common.prompt')}
|
||||||
</Box>
|
</Box>
|
||||||
<TextArea
|
<TextArea
|
||||||
@@ -38,10 +44,23 @@ const AssistantPromptSettings: React.FC<{ assistant: Assistant }> = (props) => {
|
|||||||
value={prompt}
|
value={prompt}
|
||||||
onChange={(e) => setPrompt(e.target.value)}
|
onChange={(e) => setPrompt(e.target.value)}
|
||||||
onBlur={onUpdate}
|
onBlur={onUpdate}
|
||||||
style={{ minHeight: 'calc(80vh - 150px)', maxHeight: 'calc(80vh - 150px)' }}
|
style={{ minHeight: 'calc(80vh - 200px)', maxHeight: 'calc(80vh - 150px)' }}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
<HStack width="100%" justifyContent="flex-end" mt="10px">
|
||||||
|
<Button type="primary" onClick={onOk}>
|
||||||
|
{t('common.close')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
export default AssistantPromptSettings
|
export default AssistantPromptSettings
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useAgent } from '@renderer/hooks/useAgents'
|
||||||
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
import { Menu, Modal } from 'antd'
|
import { Menu, Modal } from 'antd'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@@ -7,6 +8,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import { HStack } from '../Layout'
|
import { HStack } from '../Layout'
|
||||||
import { TopView } from '../TopView'
|
import { TopView } from '../TopView'
|
||||||
|
import AssistantMessagesSettings from './AssistantMessagesSettings'
|
||||||
import AssistantModelSettings from './AssistantModelSettings'
|
import AssistantModelSettings from './AssistantModelSettings'
|
||||||
import AssistantPromptSettings from './AssistantPromptSettings'
|
import AssistantPromptSettings from './AssistantPromptSettings'
|
||||||
|
|
||||||
@@ -18,32 +20,43 @@ interface Props extends AssistantSettingPopupShowParams {
|
|||||||
resolve: (assistant: Assistant) => void
|
resolve: (assistant: Assistant) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve }) => {
|
const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props }) => {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [menu, setMenu] = useState('prompt')
|
const [menu, setMenu] = useState('prompt')
|
||||||
const { theme } = useTheme()
|
|
||||||
|
const _useAssistant = useAssistant(props.assistant.id)
|
||||||
|
const _useAgent = useAgent(props.assistant.id)
|
||||||
|
const isAgent = props.assistant.type === 'agent'
|
||||||
|
|
||||||
|
const assistant = isAgent ? _useAgent.agent : _useAssistant.assistant
|
||||||
|
const updateAssistant = isAgent ? _useAgent.updateAgent : _useAssistant.updateAssistant
|
||||||
|
const updateAssistantSettings = isAgent ? _useAgent.updateAgentSettings : _useAssistant.updateAssistantSettings
|
||||||
|
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const afterClose = () => {
|
||||||
resolve(assistant)
|
resolve(assistant)
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
key: 'prompt',
|
key: 'prompt',
|
||||||
label: t('assistants.prompt_settings')
|
label: t('assistants.settings.prompt')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'model',
|
key: 'model',
|
||||||
label: t('assistants.model_settings')
|
label: t('assistants.settings.model')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'messages',
|
||||||
|
label: t('assistants.settings.preset_messages')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -51,21 +64,19 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
|||||||
<StyledModal
|
<StyledModal
|
||||||
open={open}
|
open={open}
|
||||||
onOk={onOk}
|
onOk={onOk}
|
||||||
onCancel={handleCancel}
|
onClose={onCancel}
|
||||||
afterClose={onClose}
|
onCancel={onCancel}
|
||||||
transitionName="ant-move-down"
|
afterClose={afterClose}
|
||||||
maskTransitionName="ant-fade"
|
|
||||||
footer={null}
|
footer={null}
|
||||||
title={assistant.name}
|
title={assistant.name}
|
||||||
|
transitionName="ant-move-down"
|
||||||
styles={{
|
styles={{
|
||||||
content: {
|
content: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: '1px solid var(--color-border)',
|
|
||||||
background: 'var(--color-background)'
|
background: 'var(--color-background)'
|
||||||
},
|
},
|
||||||
header: { padding: '10px 15px', borderBottom: '0.5px solid var(--color-border)', margin: 0 },
|
header: { padding: '10px 15px', borderBottom: '0.5px solid var(--color-border)', margin: 0 }
|
||||||
mask: { background: theme === 'light' ? 'rgba(255,255,255, 0.8)' : 'rgba(0,0,0, 0.8)' }
|
|
||||||
}}
|
}}
|
||||||
width="70vw"
|
width="70vw"
|
||||||
height="80vh"
|
height="80vh"
|
||||||
@@ -81,8 +92,28 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
|||||||
/>
|
/>
|
||||||
</LeftMenu>
|
</LeftMenu>
|
||||||
<Settings>
|
<Settings>
|
||||||
{menu === 'prompt' && <AssistantPromptSettings assistant={assistant} />}
|
{menu === 'prompt' && (
|
||||||
{menu === 'model' && <AssistantModelSettings assistant={assistant} />}
|
<AssistantPromptSettings
|
||||||
|
assistant={assistant}
|
||||||
|
updateAssistant={updateAssistant}
|
||||||
|
updateAssistantSettings={updateAssistantSettings}
|
||||||
|
onOk={onOk}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{menu === 'model' && (
|
||||||
|
<AssistantModelSettings
|
||||||
|
assistant={assistant}
|
||||||
|
updateAssistant={updateAssistant}
|
||||||
|
updateAssistantSettings={updateAssistantSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{menu === 'messages' && (
|
||||||
|
<AssistantMessagesSettings
|
||||||
|
assistant={assistant}
|
||||||
|
updateAssistant={updateAssistant}
|
||||||
|
updateAssistantSettings={updateAssistantSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Settings>
|
</Settings>
|
||||||
</HStack>
|
</HStack>
|
||||||
</StyledModal>
|
</StyledModal>
|
||||||
@@ -111,7 +142,7 @@ const StyledModal = styled(Modal)`
|
|||||||
}
|
}
|
||||||
.ant-menu-item {
|
.ant-menu-item {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -132,11 +163,7 @@ const StyledModal = styled(Modal)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default class AssistantSettingPopup {
|
export default class AssistantSettingsPopup {
|
||||||
static topviewId = 0
|
|
||||||
static hide() {
|
|
||||||
TopView.hide('AssistantSettingPopup')
|
|
||||||
}
|
|
||||||
static show(props: AssistantSettingPopupShowParams) {
|
static show(props: AssistantSettingPopupShowParams) {
|
||||||
return new Promise<Assistant>((resolve) => {
|
return new Promise<Assistant>((resolve) => {
|
||||||
TopView.show(
|
TopView.show(
|
||||||
@@ -144,10 +171,10 @@ export default class AssistantSettingPopup {
|
|||||||
{...props}
|
{...props}
|
||||||
resolve={(v) => {
|
resolve={(v) => {
|
||||||
resolve(v)
|
resolve(v)
|
||||||
this.hide()
|
TopView.hide('AssistantSettingsPopup')
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
'AssistantSettingPopup'
|
'AssistantSettingsPopup'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { TopView } from '@renderer/components/TopView'
|
|||||||
import systemAgents from '@renderer/config/agents.json'
|
import systemAgents from '@renderer/config/agents.json'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { covertAgentToAssistant } from '@renderer/services/assistant'
|
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { Agent, Assistant } from '@renderer/types'
|
import { Agent, Assistant } from '@renderer/types'
|
||||||
|
import { uuid } from '@renderer/utils'
|
||||||
import { Divider, Input, InputRef, Modal, Tag } from 'antd'
|
import { Divider, Input, InputRef, Modal, Tag } from 'antd'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -26,35 +27,24 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const { assistants, addAssistant } = useAssistants()
|
const { assistants, addAssistant } = useAssistants()
|
||||||
const inputRef = useRef<InputRef>(null)
|
const inputRef = useRef<InputRef>(null)
|
||||||
|
|
||||||
const defaultAgent: Agent = useMemo(
|
|
||||||
() => ({
|
|
||||||
id: defaultAssistant.id,
|
|
||||||
name: defaultAssistant.name,
|
|
||||||
emoji: defaultAssistant.emoji || '',
|
|
||||||
prompt: defaultAssistant.prompt,
|
|
||||||
group: 'system'
|
|
||||||
}),
|
|
||||||
[defaultAssistant.emoji, defaultAssistant.id, defaultAssistant.name, defaultAssistant.prompt]
|
|
||||||
)
|
|
||||||
|
|
||||||
const agents = useMemo(() => {
|
const agents = useMemo(() => {
|
||||||
const allAgents = [...userAgents, ...systemAgents] as Agent[]
|
const allAgents = [...userAgents, ...systemAgents] as Agent[]
|
||||||
const list = [defaultAgent, ...allAgents.filter((agent) => !assistants.map((a) => a.id).includes(agent.id))]
|
const list = [defaultAssistant, ...allAgents.filter((agent) => !assistants.map((a) => a.id).includes(agent.id))]
|
||||||
return searchText
|
return searchText
|
||||||
? list.filter((agent) => agent.name.toLowerCase().includes(searchText.trim().toLocaleLowerCase()))
|
? list.filter((agent) => agent.name.toLowerCase().includes(searchText.trim().toLocaleLowerCase()))
|
||||||
: list
|
: list
|
||||||
}, [assistants, defaultAgent, searchText, userAgents])
|
}, [assistants, defaultAssistant, searchText, userAgents])
|
||||||
|
|
||||||
const onCreateAssistant = (agent: Agent) => {
|
const onCreateAssistant = async (agent: Agent) => {
|
||||||
if (agent.id !== 'default') {
|
let assistant: Assistant
|
||||||
if (assistants.map((a) => a.id).includes(String(agent.id))) {
|
|
||||||
return
|
if (agent.id === 'default') {
|
||||||
}
|
assistant = { ...agent, id: uuid() }
|
||||||
|
addAssistant(assistant)
|
||||||
|
} else {
|
||||||
|
assistant = await createAssistantFromAgent(agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const assistant = covertAgentToAssistant(agent)
|
|
||||||
|
|
||||||
addAssistant(assistant)
|
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
||||||
resolve(assistant)
|
resolve(assistant)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@@ -79,8 +69,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
open={open}
|
open={open}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
afterClose={onClose}
|
afterClose={onClose}
|
||||||
transitionName="ant-move-down"
|
transitionName="ant-move-up"
|
||||||
maskTransitionName="ant-fade"
|
|
||||||
styles={{ content: { borderRadius: 20, padding: 0, overflow: 'hidden', paddingBottom: 20 } }}
|
styles={{ content: { borderRadius: 20, padding: 0, overflow: 'hidden', paddingBottom: 20 } }}
|
||||||
closeIcon={null}
|
closeIcon={null}
|
||||||
footer={null}>
|
footer={null}>
|
||||||
@@ -102,7 +91,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
size="middle"
|
size="middle"
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Divider style={{ margin: 0 }} />
|
<Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} />
|
||||||
<Container>
|
<Container>
|
||||||
{agents.map((agent) => (
|
{agents.map((agent) => (
|
||||||
<AgentItem
|
<AgentItem
|
||||||
@@ -112,8 +101,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
<HStack alignItems="center" gap={5}>
|
<HStack alignItems="center" gap={5}>
|
||||||
{agent.emoji} {agent.name}
|
{agent.emoji} {agent.name}
|
||||||
</HStack>
|
</HStack>
|
||||||
{agent.group === 'system' && <Tag color="green">{t('agents.tag.system')}</Tag>}
|
{agent.id === 'default' && <Tag color="green">{t('agents.tag.system')}</Tag>}
|
||||||
{agent.group === 'user' && <Tag color="orange">{t('agents.tag.user')}</Tag>}
|
{agent.type === 'agent' && <Tag color="orange">{t('agents.tag.agent')}</Tag>}
|
||||||
</AgentItem>
|
</AgentItem>
|
||||||
))}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
182
src/renderer/src/components/Popups/SelectModelPopup.tsx
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import { SearchOutlined } from '@ant-design/icons'
|
||||||
|
import VisionIcon from '@renderer/components/Icons/VisionIcon'
|
||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { getModelLogo, isVisionModel } from '@renderer/config/models'
|
||||||
|
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 } from 'lodash'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { HStack } from '../Layout'
|
||||||
|
import Scrollbar from '../Scrollbar'
|
||||||
|
|
||||||
|
type MenuItem = Required<MenuProps>['items'][number]
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
model?: Model
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopupContainerProps extends Props {
|
||||||
|
resolve: (value: Model | undefined) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const inputRef = useRef<InputRef>(null)
|
||||||
|
const { providers } = useProviders()
|
||||||
|
|
||||||
|
const filteredItems: MenuItem[] = providers
|
||||||
|
.filter((p) => p.models && p.models.length > 0)
|
||||||
|
.map((p) => ({
|
||||||
|
key: p.id,
|
||||||
|
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||||
|
type: 'group',
|
||||||
|
children: reverse(sortBy(p.models, 'name'))
|
||||||
|
.filter((m) =>
|
||||||
|
[m.name + m.provider + t('provider.' + p.id)].join('').toLowerCase().includes(searchText.toLowerCase())
|
||||||
|
)
|
||||||
|
.map((m) => ({
|
||||||
|
key: getModelUniqId(m),
|
||||||
|
label: (
|
||||||
|
<ModelItem>
|
||||||
|
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
||||||
|
</ModelItem>
|
||||||
|
),
|
||||||
|
icon: (
|
||||||
|
<Avatar src={getModelLogo(m?.id || '')} size={24}>
|
||||||
|
{first(m?.name)}
|
||||||
|
</Avatar>
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
resolve(m)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
.filter((item) => item.children && item.children.length > 0) as MenuItem[]
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = async () => {
|
||||||
|
resolve(undefined)
|
||||||
|
SelectModelPopup.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
open && setTimeout(() => inputRef.current?.focus(), 0)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
centered
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
transitionName="ant-move-down"
|
||||||
|
styles={{ content: { borderRadius: 20, padding: 0, overflow: 'hidden', paddingBottom: 20 } }}
|
||||||
|
closeIcon={null}
|
||||||
|
footer={null}>
|
||||||
|
<HStack style={{ padding: '0 12px', marginTop: 5 }}>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<SearchIcon>
|
||||||
|
<SearchOutlined />
|
||||||
|
</SearchIcon>
|
||||||
|
}
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={t('model.search')}
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
allowClear
|
||||||
|
autoFocus
|
||||||
|
style={{ paddingLeft: 0 }}
|
||||||
|
bordered={false}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
<Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} />
|
||||||
|
<Scrollbar style={{ height: '50vh' }}>
|
||||||
|
<Container>
|
||||||
|
{filteredItems.length > 0 ? (
|
||||||
|
<StyledMenu
|
||||||
|
items={filteredItems}
|
||||||
|
selectedKeys={model ? [getModelUniqId(model)] : []}
|
||||||
|
mode="inline"
|
||||||
|
inlineIndent={6}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyState>
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
</EmptyState>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Scrollbar>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
margin-top: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const StyledMenu = styled(Menu)`
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 5px;
|
||||||
|
margin-top: -10px;
|
||||||
|
max-height: calc(60vh - 50px);
|
||||||
|
|
||||||
|
.ant-menu-item-group-title {
|
||||||
|
padding: 5px 10px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-item {
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ModelItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const EmptyState = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 200px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SearchIcon = styled.div`
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
margin-right: 2px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default class SelectModelPopup {
|
||||||
|
static topviewId = 0
|
||||||
|
static hide() {
|
||||||
|
TopView.hide('SelectModelPopup')
|
||||||
|
}
|
||||||
|
static show(params: Props) {
|
||||||
|
return new Promise<Model | undefined>((resolve) => {
|
||||||
|
TopView.show(<PopupContainer {...params} resolve={resolve} />, 'SelectModelPopup')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,7 +55,6 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
|||||||
width="60vw"
|
width="60vw"
|
||||||
style={{ maxHeight: '70vh' }}
|
style={{ maxHeight: '70vh' }}
|
||||||
transitionName="ant-move-down"
|
transitionName="ant-move-down"
|
||||||
maskTransitionName="ant-fade"
|
|
||||||
okText={t('common.save')}
|
okText={t('common.save')}
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
open={open}
|
open={open}
|
||||||
|
|||||||
57
src/renderer/src/components/Scrollbar/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { throttle } from 'lodash'
|
||||||
|
import { FC, forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
right?: boolean
|
||||||
|
ref?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Scrollbar: FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||||
|
const [isScrolling, setIsScrolling] = useState(false)
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
|
const handleScroll = useCallback(
|
||||||
|
throttle(() => {
|
||||||
|
setIsScrolling(true)
|
||||||
|
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutRef.current = setTimeout(() => setIsScrolling(false), 1500) // 增加到 2 秒
|
||||||
|
}, 200),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container {...props} isScrolling={isScrolling} onScroll={handleScroll} ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
Scrollbar.displayName = 'Scrollbar'
|
||||||
|
|
||||||
|
const Container = styled.div<{ isScrolling: boolean; right?: boolean }>`
|
||||||
|
overflow-y: auto;
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
transition: background 2s ease;
|
||||||
|
background: ${(props) =>
|
||||||
|
props.isScrolling ? `var(--color-scrollbar-thumb${props.right ? '-right' : ''})` : 'transparent'};
|
||||||
|
&:hover {
|
||||||
|
background: ${(props) =>
|
||||||
|
props.isScrolling ? `var(--color-scrollbar-thumb${props.right ? '-right' : ''}-hover)` : 'transparent'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default Scrollbar
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FolderOutlined, TranslationOutlined } from '@ant-design/icons'
|
import { FileSearchOutlined, FolderOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
@@ -23,6 +23,7 @@ const Sidebar: FC = () => {
|
|||||||
const { windowStyle } = useSettings()
|
const { windowStyle } = useSettings()
|
||||||
|
|
||||||
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||||
|
const isRoutes = (path: string): string => (pathname.startsWith(path) ? 'active' : '')
|
||||||
|
|
||||||
const onEditUser = () => UserPopup.show()
|
const onEditUser = () => UserPopup.show()
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ const Sidebar: FC = () => {
|
|||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={() => to('/agents')}>
|
<StyledLink onClick={() => to('/agents')}>
|
||||||
<Icon className={isRoute('/agents')}>
|
<Icon className={isRoutes('/agents')}>
|
||||||
<i className="iconfont icon-business-smart-assistant" />
|
<i className="iconfont icon-business-smart-assistant" />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
@@ -72,6 +73,11 @@ const Sidebar: FC = () => {
|
|||||||
<FolderOutlined />
|
<FolderOutlined />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
|
<StyledLink onClick={() => to('/messages')}>
|
||||||
|
<Icon className={isRoutes('/messages')}>
|
||||||
|
<FileSearchOutlined />
|
||||||
|
</Icon>
|
||||||
|
</StyledLink>
|
||||||
</Menus>
|
</Menus>
|
||||||
</MainMenus>
|
</MainMenus>
|
||||||
<Menus onClick={MinApp.onClose}>
|
<Menus onClick={MinApp.onClose}>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const isWindows = platform === 'win32' || platform === 'win64'
|
|||||||
export const isLinux = platform === 'linux'
|
export const isLinux = platform === 'linux'
|
||||||
|
|
||||||
export const imageExts = ['.jpg', '.png', '.jpeg']
|
export const imageExts = ['.jpg', '.png', '.jpeg']
|
||||||
|
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||||
export const textExts = [
|
export const textExts = [
|
||||||
'.txt', // 普通文本文件
|
'.txt', // 普通文本文件
|
||||||
'.md', // Markdown 文件
|
'.md', // Markdown 文件
|
||||||
@@ -31,8 +32,6 @@ export const textExts = [
|
|||||||
'.conf', // 配置文件
|
'.conf', // 配置文件
|
||||||
'.config', // 配置文件
|
'.config', // 配置文件
|
||||||
'.env', // 环境变量文件
|
'.env', // 环境变量文件
|
||||||
'.properties', // 配置属性文件
|
|
||||||
'.latex', // LaTeX 文档文件
|
|
||||||
'.rst', // reStructuredText 文件
|
'.rst', // reStructuredText 文件
|
||||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||||
@@ -52,7 +51,6 @@ export const textExts = [
|
|||||||
'.styl', // Stylus CSS 预处理器文件
|
'.styl', // Stylus CSS 预处理器文件
|
||||||
'.coffee', // CoffeeScript 文件
|
'.coffee', // CoffeeScript 文件
|
||||||
'.ino', // Arduino 代码文件
|
'.ino', // Arduino 代码文件
|
||||||
'.ino', // Arduino 代码文件
|
|
||||||
'.asm', // Assembly 语言文件
|
'.asm', // Assembly 语言文件
|
||||||
'.go', // Go 语言文件
|
'.go', // Go 语言文件
|
||||||
'.scala', // Scala 语言文件
|
'.scala', // Scala 语言文件
|
||||||
@@ -96,6 +94,6 @@ export const textExts = [
|
|||||||
'.mm', // Objective-C++ 源文件
|
'.mm', // Objective-C++ 源文件
|
||||||
'.gradle', // Gradle 构建文件
|
'.gradle', // Gradle 构建文件
|
||||||
'.groovy', // Gradle 构建文件
|
'.groovy', // Gradle 构建文件
|
||||||
'.gradle', // Gradle 构建文件
|
'.kts', // Kotlin Script 文件
|
||||||
'.kts' // Kotlin Script 文件
|
'.java' // Java 代码文件
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.png'
|
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.png'
|
||||||
import AiSearchAppLogo from '@renderer/assets/images/apps/ai-search.png'
|
import AiSearchAppLogo from '@renderer/assets/images/apps/ai-search.png'
|
||||||
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png'
|
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png'
|
||||||
import FeloAppLogo from '@renderer/assets/images/apps/felo.png'
|
|
||||||
import BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp'
|
import BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp'
|
||||||
|
import BoltAppLogo from '@renderer/assets/images/apps/bolt.svg'
|
||||||
import DevvAppLogo from '@renderer/assets/images/apps/devv.png'
|
import DevvAppLogo from '@renderer/assets/images/apps/devv.png'
|
||||||
import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png'
|
import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png'
|
||||||
|
import FeloAppLogo from '@renderer/assets/images/apps/felo.png'
|
||||||
import GeminiAppLogo from '@renderer/assets/images/apps/gemini.png'
|
import GeminiAppLogo from '@renderer/assets/images/apps/gemini.png'
|
||||||
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg'
|
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg'
|
||||||
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
|
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
|
||||||
@@ -207,6 +208,13 @@ const _apps: MinAppType[] = [
|
|||||||
logo: FeloAppLogo,
|
logo: FeloAppLogo,
|
||||||
url: 'https://felo.ai/',
|
url: 'https://felo.ai/',
|
||||||
bodered: true
|
bodered: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bolt',
|
||||||
|
name: 'bolt',
|
||||||
|
logo: BoltAppLogo,
|
||||||
|
url: 'https://bolt.new/',
|
||||||
|
bodered: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import Ai360ModelLogo from '@renderer/assets/images/models/360.png'
|
import Ai360ModelLogo from '@renderer/assets/images/models/360.png'
|
||||||
import Ai360ModelLogoDark from '@renderer/assets/images/models/360_dark.png'
|
import Ai360ModelLogoDark from '@renderer/assets/images/models/360_dark.png'
|
||||||
|
import AdeptModelLogo from '@renderer/assets/images/models/adept.png'
|
||||||
|
import AdeptModelLogoDark from '@renderer/assets/images/models/adept_dark.png'
|
||||||
import Ai21ModelLogo from '@renderer/assets/images/models/ai21.png'
|
import Ai21ModelLogo from '@renderer/assets/images/models/ai21.png'
|
||||||
import Ai21ModelLogoDark from '@renderer/assets/images/models/ai21_dark.png'
|
import Ai21ModelLogoDark from '@renderer/assets/images/models/ai21_dark.png'
|
||||||
import AimassModelLogo from '@renderer/assets/images/models/aimass.png'
|
import AimassModelLogo from '@renderer/assets/images/models/aimass.png'
|
||||||
import AimassModelLogoDark from '@renderer/assets/images/models/aimass_dark.png'
|
import AimassModelLogoDark from '@renderer/assets/images/models/aimass_dark.png'
|
||||||
import MinimaxModelLogo from '@renderer/assets/images/models/minimax.png'
|
import AisingaporeModelLogo from '@renderer/assets/images/models/aisingapore.png'
|
||||||
import MinimaxModelLogoDark from '@renderer/assets/images/models/minimax_dark.png'
|
import AisingaporeModelLogoDark from '@renderer/assets/images/models/aisingapore_dark.png'
|
||||||
import BaichuanModelLogo from '@renderer/assets/images/models/baichuan.png'
|
import BaichuanModelLogo from '@renderer/assets/images/models/baichuan.png'
|
||||||
import BaichuanModelLogoDark from '@renderer/assets/images/models/baichuan_dark.png'
|
import BaichuanModelLogoDark from '@renderer/assets/images/models/baichuan_dark.png'
|
||||||
|
import BigcodeModelLogo from '@renderer/assets/images/models/bigcode.png'
|
||||||
|
import BigcodeModelLogoDark from '@renderer/assets/images/models/bigcode_dark.png'
|
||||||
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.png'
|
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.png'
|
||||||
import ChatGLMModelLogoDark from '@renderer/assets/images/models/chatglm_dark.png'
|
import ChatGLMModelLogoDark from '@renderer/assets/images/models/chatglm_dark.png'
|
||||||
import ChatGptModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
import ChatGptModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
||||||
@@ -24,6 +28,8 @@ import DalleModelLogoDark from '@renderer/assets/images/models/dalle_dark.png'
|
|||||||
import DbrxModelLogo from '@renderer/assets/images/models/dbrx.png'
|
import DbrxModelLogo from '@renderer/assets/images/models/dbrx.png'
|
||||||
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
||||||
import DeepSeekModelLogoDark from '@renderer/assets/images/models/deepseek_dark.png'
|
import DeepSeekModelLogoDark from '@renderer/assets/images/models/deepseek_dark.png'
|
||||||
|
import DianxinModelLogo from '@renderer/assets/images/models/dianxin.png'
|
||||||
|
import DianxinModelLogoDark from '@renderer/assets/images/models/dianxin_dark.png'
|
||||||
import DoubaoModelLogo from '@renderer/assets/images/models/doubao.png'
|
import DoubaoModelLogo from '@renderer/assets/images/models/doubao.png'
|
||||||
import DoubaoModelLogoDark from '@renderer/assets/images/models/doubao_dark.png'
|
import DoubaoModelLogoDark from '@renderer/assets/images/models/doubao_dark.png'
|
||||||
import EmbeddingModelLogo from '@renderer/assets/images/models/embedding.png'
|
import EmbeddingModelLogo from '@renderer/assets/images/models/embedding.png'
|
||||||
@@ -36,6 +42,8 @@ import GeminiModelLogo from '@renderer/assets/images/models/gemini.png'
|
|||||||
import GeminiModelLogoDark from '@renderer/assets/images/models/gemini_dark.png'
|
import GeminiModelLogoDark from '@renderer/assets/images/models/gemini_dark.png'
|
||||||
import GemmaModelLogo from '@renderer/assets/images/models/gemma.png'
|
import GemmaModelLogo from '@renderer/assets/images/models/gemma.png'
|
||||||
import GemmaModelLogoDark from '@renderer/assets/images/models/gemma_dark.png'
|
import GemmaModelLogoDark from '@renderer/assets/images/models/gemma_dark.png'
|
||||||
|
import GoogleModelLogo from '@renderer/assets/images/models/google.png'
|
||||||
|
import GoogleModelLogoDark from '@renderer/assets/images/models/google.png'
|
||||||
import GorkModelLogo from '@renderer/assets/images/models/gork.png'
|
import GorkModelLogo from '@renderer/assets/images/models/gork.png'
|
||||||
import GorkModelLogoDark from '@renderer/assets/images/models/gork_dark.png'
|
import GorkModelLogoDark from '@renderer/assets/images/models/gork_dark.png'
|
||||||
import ChatGPT35ModelLogo from '@renderer/assets/images/models/gpt_3.5.png'
|
import ChatGPT35ModelLogo from '@renderer/assets/images/models/gpt_3.5.png'
|
||||||
@@ -49,8 +57,12 @@ import GrypheModelLogo from '@renderer/assets/images/models/gryphe.png'
|
|||||||
import GrypheModelLogoDark from '@renderer/assets/images/models/gryphe_dark.png'
|
import GrypheModelLogoDark from '@renderer/assets/images/models/gryphe_dark.png'
|
||||||
import HailuoModelLogo from '@renderer/assets/images/models/hailuo.png'
|
import HailuoModelLogo from '@renderer/assets/images/models/hailuo.png'
|
||||||
import HailuoModelLogoDark from '@renderer/assets/images/models/hailuo_dark.png'
|
import HailuoModelLogoDark from '@renderer/assets/images/models/hailuo_dark.png'
|
||||||
|
import HuggingfaceModelLogo from '@renderer/assets/images/models/huggingface.png'
|
||||||
|
import HuggingfaceModelLogoDark from '@renderer/assets/images/models/huggingface_dark.png'
|
||||||
import HunyuanModelLogo from '@renderer/assets/images/models/hunyuan.png'
|
import HunyuanModelLogo from '@renderer/assets/images/models/hunyuan.png'
|
||||||
import HunyuanModelLogoDark from '@renderer/assets/images/models/hunyuan_dark.png'
|
import HunyuanModelLogoDark from '@renderer/assets/images/models/hunyuan_dark.png'
|
||||||
|
import IbmModelLogo from '@renderer/assets/images/models/ibm.png'
|
||||||
|
import IbmModelLogoDark from '@renderer/assets/images/models/ibm_dark.png'
|
||||||
import InternlmModelLogo from '@renderer/assets/images/models/internlm.png'
|
import InternlmModelLogo from '@renderer/assets/images/models/internlm.png'
|
||||||
import InternlmModelLogoDark from '@renderer/assets/images/models/internlm_dark.png'
|
import InternlmModelLogoDark from '@renderer/assets/images/models/internlm_dark.png'
|
||||||
import KeLingModelLogo from '@renderer/assets/images/models/keling.png'
|
import KeLingModelLogo from '@renderer/assets/images/models/keling.png'
|
||||||
@@ -63,22 +75,30 @@ import LumaModelLogo from '@renderer/assets/images/models/luma.png'
|
|||||||
import LumaModelLogoDark from '@renderer/assets/images/models/luma_dark.png'
|
import LumaModelLogoDark from '@renderer/assets/images/models/luma_dark.png'
|
||||||
import MagicModelLogo from '@renderer/assets/images/models/magic.png'
|
import MagicModelLogo from '@renderer/assets/images/models/magic.png'
|
||||||
import MagicModelLogoDark from '@renderer/assets/images/models/magic_dark.png'
|
import MagicModelLogoDark from '@renderer/assets/images/models/magic_dark.png'
|
||||||
|
import MediatekModelLogo from '@renderer/assets/images/models/mediatek.png'
|
||||||
|
import MediatekModelLogoDark from '@renderer/assets/images/models/mediatek_dark.png'
|
||||||
import MicrosoftModelLogo from '@renderer/assets/images/models/microsoft.png'
|
import MicrosoftModelLogo from '@renderer/assets/images/models/microsoft.png'
|
||||||
import MicrosoftModelLogoDark from '@renderer/assets/images/models/microsoft_dark.png'
|
import MicrosoftModelLogoDark from '@renderer/assets/images/models/microsoft_dark.png'
|
||||||
import MidjourneyModelLogo from '@renderer/assets/images/models/midjourney.png'
|
import MidjourneyModelLogo from '@renderer/assets/images/models/midjourney.png'
|
||||||
import MidjourneyModelLogoDark from '@renderer/assets/images/models/midjourney_dark.png'
|
import MidjourneyModelLogoDark from '@renderer/assets/images/models/midjourney_dark.png'
|
||||||
import MinicpmModelLogo from '@renderer/assets/images/models/minicpm.webp'
|
import MinicpmModelLogo from '@renderer/assets/images/models/minicpm.webp'
|
||||||
import MinicpmModelLogoDark from '@renderer/assets/images/models/minicpm.webp'
|
import MinicpmModelLogoDark from '@renderer/assets/images/models/minicpm.webp'
|
||||||
|
import MinimaxModelLogo from '@renderer/assets/images/models/minimax.png'
|
||||||
|
import MinimaxModelLogoDark from '@renderer/assets/images/models/minimax_dark.png'
|
||||||
import MistralModelLogo from '@renderer/assets/images/models/mixtral.png'
|
import MistralModelLogo from '@renderer/assets/images/models/mixtral.png'
|
||||||
import MistralModelLogoDark from '@renderer/assets/images/models/mixtral_dark.png'
|
import MistralModelLogoDark from '@renderer/assets/images/models/mixtral_dark.png'
|
||||||
import MoonshotModelLogo from '@renderer/assets/images/models/moonshot.png'
|
import MoonshotModelLogo from '@renderer/assets/images/models/moonshot.png'
|
||||||
import MoonshotModelLogoDark from '@renderer/assets/images/models/moonshot_dark.png'
|
import MoonshotModelLogoDark from '@renderer/assets/images/models/moonshot_dark.png'
|
||||||
import NousResearchModelLogo from '@renderer/assets/images/models/nousresearch.png'
|
import NousResearchModelLogo from '@renderer/assets/images/models/nousresearch.png'
|
||||||
import NousResearchModelLogoDark from '@renderer/assets/images/models/nousresearch.png'
|
import NousResearchModelLogoDark from '@renderer/assets/images/models/nousresearch.png'
|
||||||
|
import NvidiaModelLogo from '@renderer/assets/images/models/nvidia.png'
|
||||||
|
import NvidiaModelLogoDark from '@renderer/assets/images/models/nvidia_dark.png'
|
||||||
import PalmModelLogo from '@renderer/assets/images/models/palm.png'
|
import PalmModelLogo from '@renderer/assets/images/models/palm.png'
|
||||||
import PalmModelLogoDark from '@renderer/assets/images/models/palm_dark.png'
|
import PalmModelLogoDark from '@renderer/assets/images/models/palm_dark.png'
|
||||||
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
|
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
|
||||||
import QwenModelLogoDark from '@renderer/assets/images/models/qwen_dark.png'
|
import QwenModelLogoDark from '@renderer/assets/images/models/qwen_dark.png'
|
||||||
|
import RakutenaiModelLogo from '@renderer/assets/images/models/rakutenai.png'
|
||||||
|
import RakutenaiModelLogoDark from '@renderer/assets/images/models/rakutenai_dark.png'
|
||||||
import SparkDeskModelLogo from '@renderer/assets/images/models/sparkdesk.png'
|
import SparkDeskModelLogo from '@renderer/assets/images/models/sparkdesk.png'
|
||||||
import SparkDeskModelLogoDark from '@renderer/assets/images/models/sparkdesk_dark.png'
|
import SparkDeskModelLogoDark from '@renderer/assets/images/models/sparkdesk_dark.png'
|
||||||
import StabilityModelLogo from '@renderer/assets/images/models/stability.png'
|
import StabilityModelLogo from '@renderer/assets/images/models/stability.png'
|
||||||
@@ -87,6 +107,10 @@ import StepModelLogo from '@renderer/assets/images/models/step.png'
|
|||||||
import StepModelLogoDark from '@renderer/assets/images/models/step_dark.png'
|
import StepModelLogoDark from '@renderer/assets/images/models/step_dark.png'
|
||||||
import SunoModelLogo from '@renderer/assets/images/models/suno.png'
|
import SunoModelLogo from '@renderer/assets/images/models/suno.png'
|
||||||
import SunoModelLogoDark from '@renderer/assets/images/models/suno_dark.png'
|
import SunoModelLogoDark from '@renderer/assets/images/models/suno_dark.png'
|
||||||
|
import TeleModelLogo from '@renderer/assets/images/models/tele.png'
|
||||||
|
import TeleModelLogoDark from '@renderer/assets/images/models/tele_dark.png'
|
||||||
|
import UpstageModelLogo from '@renderer/assets/images/models/upstage.png'
|
||||||
|
import UpstageModelLogoDark from '@renderer/assets/images/models/upstage_dark.png'
|
||||||
import ViduModelLogo from '@renderer/assets/images/models/vidu.png'
|
import ViduModelLogo from '@renderer/assets/images/models/vidu.png'
|
||||||
import ViduModelLogoDark from '@renderer/assets/images/models/vidu_dark.png'
|
import ViduModelLogoDark from '@renderer/assets/images/models/vidu_dark.png'
|
||||||
import WenxinModelLogo from '@renderer/assets/images/models/wenxin.png'
|
import WenxinModelLogo from '@renderer/assets/images/models/wenxin.png'
|
||||||
@@ -96,7 +120,7 @@ import YiModelLogoDark from '@renderer/assets/images/models/yi_dark.png'
|
|||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
const allowedModels = [
|
const visionAllowedModels = [
|
||||||
'llava',
|
'llava',
|
||||||
'moondream',
|
'moondream',
|
||||||
'minicpm',
|
'minicpm',
|
||||||
@@ -105,11 +129,19 @@ const allowedModels = [
|
|||||||
'vision',
|
'vision',
|
||||||
'glm-4v',
|
'glm-4v',
|
||||||
'qwen-vl',
|
'qwen-vl',
|
||||||
|
'qwen2-vl',
|
||||||
|
'internvl2',
|
||||||
'gpt-4(?:-[\\w-]+)',
|
'gpt-4(?:-[\\w-]+)',
|
||||||
'gpt-4o(?:-[\\w-]+)?'
|
'gpt-4o(?:-[\\w-]+)?'
|
||||||
]
|
]
|
||||||
const excludedModels = ['gpt-4-\\d+-preview', 'gpt-4-turbo-preview', 'gpt-4-32k', 'gpt-4-\\d+']
|
|
||||||
const VISION_REGEX = new RegExp(`\\b(?!(?:${excludedModels.join('|')})\\b)(${allowedModels.join('|')})\\b`, 'i')
|
const visionExcludedModels = ['gpt-4-\\d+-preview', 'gpt-4-turbo-preview', 'gpt-4-32k', 'gpt-4-\\d+']
|
||||||
|
|
||||||
|
const VISION_REGEX = new RegExp(
|
||||||
|
`\\b(?!(?:${visionExcludedModels.join('|')})\\b)(${visionAllowedModels.join('|')})\\b`,
|
||||||
|
'i'
|
||||||
|
)
|
||||||
|
|
||||||
const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
|
const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
|
||||||
const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|base|retrieval|uae-)/i
|
const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|base|retrieval|uae-)/i
|
||||||
const NOT_SUPPORTED_REGEX = /(?:^text-|embed|tts|rerank|whisper|speech|davinci|babbage|bge-|base|retrieval|uae-)/i
|
const NOT_SUPPORTED_REGEX = /(?:^text-|embed|tts|rerank|whisper|speech|davinci|babbage|bge-|base|retrieval|uae-)/i
|
||||||
@@ -183,6 +215,7 @@ export function getModelLogo(modelId: string) {
|
|||||||
sparkdesk: isLight ? SparkDeskModelLogo : SparkDeskModelLogoDark,
|
sparkdesk: isLight ? SparkDeskModelLogo : SparkDeskModelLogoDark,
|
||||||
generalv: isLight ? SparkDeskModelLogo : SparkDeskModelLogoDark,
|
generalv: isLight ? SparkDeskModelLogo : SparkDeskModelLogoDark,
|
||||||
wizardlm: isLight ? MicrosoftModelLogo : MicrosoftModelLogoDark,
|
wizardlm: isLight ? MicrosoftModelLogo : MicrosoftModelLogoDark,
|
||||||
|
microsoft: isLight ? MicrosoftModelLogo : MicrosoftModelLogoDark,
|
||||||
hermes: isLight ? NousResearchModelLogo : NousResearchModelLogoDark,
|
hermes: isLight ? NousResearchModelLogo : NousResearchModelLogoDark,
|
||||||
gryphe: isLight ? GrypheModelLogo : GrypheModelLogoDark,
|
gryphe: isLight ? GrypheModelLogo : GrypheModelLogoDark,
|
||||||
suno: isLight ? SunoModelLogo : SunoModelLogoDark,
|
suno: isLight ? SunoModelLogo : SunoModelLogoDark,
|
||||||
@@ -192,7 +225,19 @@ export function getModelLogo(modelId: string) {
|
|||||||
'vidu-': isLight ? ViduModelLogo : ViduModelLogoDark,
|
'vidu-': isLight ? ViduModelLogo : ViduModelLogoDark,
|
||||||
ai21: isLight ? Ai21ModelLogo : Ai21ModelLogoDark,
|
ai21: isLight ? Ai21ModelLogo : Ai21ModelLogoDark,
|
||||||
'jamba-': isLight ? Ai21ModelLogo : Ai21ModelLogoDark,
|
'jamba-': isLight ? Ai21ModelLogo : Ai21ModelLogoDark,
|
||||||
mythomax: isLight ? GrypheModelLogo : GrypheModelLogoDark
|
mythomax: isLight ? GrypheModelLogo : GrypheModelLogoDark,
|
||||||
|
nvidia: isLight ? NvidiaModelLogo : NvidiaModelLogoDark,
|
||||||
|
dianxin: isLight ? DianxinModelLogo : DianxinModelLogoDark,
|
||||||
|
tele: isLight ? TeleModelLogo : TeleModelLogoDark,
|
||||||
|
adept: isLight ? AdeptModelLogo : AdeptModelLogoDark,
|
||||||
|
aisingapore: isLight ? AisingaporeModelLogo : AisingaporeModelLogoDark,
|
||||||
|
bigcode: isLight ? BigcodeModelLogo : BigcodeModelLogoDark,
|
||||||
|
mediatek: isLight ? MediatekModelLogo : MediatekModelLogoDark,
|
||||||
|
upstage: isLight ? UpstageModelLogo : UpstageModelLogoDark,
|
||||||
|
rakutenai: isLight ? RakutenaiModelLogo : RakutenaiModelLogoDark,
|
||||||
|
ibm: isLight ? IbmModelLogo : IbmModelLogoDark,
|
||||||
|
'google/': isLight ? GoogleModelLogo : GoogleModelLogoDark,
|
||||||
|
hugging: isLight ? HuggingfaceModelLogo : HuggingfaceModelLogoDark
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in logoMap) {
|
for (const key in logoMap) {
|
||||||
@@ -207,6 +252,30 @@ export function getModelLogo(modelId: string) {
|
|||||||
export const SYSTEM_MODELS: Record<string, Model[]> = {
|
export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||||
ollama: [],
|
ollama: [],
|
||||||
silicon: [
|
silicon: [
|
||||||
|
{
|
||||||
|
id: 'Qwen/Qwen2.5-72B-Instruct',
|
||||||
|
provider: 'silicon',
|
||||||
|
name: 'Qwen2.5-72B-Instruct',
|
||||||
|
group: 'Qwen2.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Qwen/Qwen2.5-32B-Instruct',
|
||||||
|
provider: 'silicon',
|
||||||
|
name: 'Qwen2.5-32B-Instruct',
|
||||||
|
group: 'Qwen2.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Qwen/Qwen2.5-14B-Instruct',
|
||||||
|
provider: 'silicon',
|
||||||
|
name: 'Qwen2.5-14B-Instruct',
|
||||||
|
group: 'Qwen2.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Qwen/Qwen2.5-7B-Instruct',
|
||||||
|
provider: 'silicon',
|
||||||
|
name: 'Qwen2.5-7B-Instruct',
|
||||||
|
group: 'Qwen2.5'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'Qwen/Qwen2-7B-Instruct',
|
id: 'Qwen/Qwen2-7B-Instruct',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
@@ -262,6 +331,38 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
provider: 'openai',
|
provider: 'openai',
|
||||||
name: ' GPT-4',
|
name: ' GPT-4',
|
||||||
group: 'GPT 4'
|
group: 'GPT 4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gpt-3.5-turbo',
|
||||||
|
provider: 'openai',
|
||||||
|
name: ' GPT-3.5-turbo',
|
||||||
|
group: 'GPT 3.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'o1-mini',
|
||||||
|
provider: 'openai',
|
||||||
|
name: ' o1-mini',
|
||||||
|
group: 'o1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'o1-preview',
|
||||||
|
provider: 'openai',
|
||||||
|
name: ' o1-preview',
|
||||||
|
group: 'o1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'azure-openai': [
|
||||||
|
{
|
||||||
|
id: 'gpt-4o',
|
||||||
|
provider: 'azure-openai',
|
||||||
|
name: ' GPT-4o',
|
||||||
|
group: 'GPT 4o'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gpt-4o-mini',
|
||||||
|
provider: 'azure-openai',
|
||||||
|
name: ' GPT-4o-mini',
|
||||||
|
group: 'GPT 4o'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
gemini: [
|
gemini: [
|
||||||
@@ -318,6 +419,32 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
group: 'DeepSeek Coder'
|
group: 'DeepSeek Coder'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
together: [
|
||||||
|
{
|
||||||
|
id: 'meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo',
|
||||||
|
provider: 'together',
|
||||||
|
name: 'Llama-3.2-11B-Vision',
|
||||||
|
group: 'Llama-3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo',
|
||||||
|
provider: 'together',
|
||||||
|
name: 'Llama-3.2-90B-Vision',
|
||||||
|
group: 'Llama-3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'google/gemma-2-27b-it',
|
||||||
|
provider: 'together',
|
||||||
|
name: 'gemma-2-27b-it',
|
||||||
|
group: 'Gemma'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'google/gemma-2-9b-it',
|
||||||
|
provider: 'together',
|
||||||
|
name: 'gemma-2-9b-it',
|
||||||
|
group: 'Gemma'
|
||||||
|
}
|
||||||
|
],
|
||||||
ocoolai: [
|
ocoolai: [
|
||||||
{
|
{
|
||||||
id: 'gpt-4o',
|
id: 'gpt-4o',
|
||||||
@@ -396,6 +523,48 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
provider: 'ocoolai',
|
provider: 'ocoolai',
|
||||||
name: 'claude-3-haiku-20240307',
|
name: 'claude-3-haiku-20240307',
|
||||||
group: 'Anthropic'
|
group: 'Anthropic'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-pro',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'gemini-pro',
|
||||||
|
group: 'Gemini'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-1.5-pro',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'gemini-1.5-pro',
|
||||||
|
group: 'Gemini'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'Llama-3.2-90B-Vision-Instruct-Turbo',
|
||||||
|
group: 'Llama-3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'Llama-3.2-11B-Vision-Instruct-Turbo',
|
||||||
|
group: 'Llama-3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meta-llama/Llama-3.2-3B-Vision-Instruct-Turbo',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'Llama-3.2-3B-Vision-Instruct-Turbo',
|
||||||
|
group: 'Llama-3.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'google/gemma-2-27b-it',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'gemma-2-27b-it',
|
||||||
|
group: 'Gemma'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'google/gemma-2-9b-it',
|
||||||
|
provider: 'ocoolai',
|
||||||
|
name: 'gemma-2-9b-it',
|
||||||
|
group: 'Gemma'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
github: [
|
github: [
|
||||||
@@ -534,7 +703,7 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
group: 'Baichuan3'
|
group: 'Baichuan3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dashscope: [
|
bailian: [
|
||||||
{
|
{
|
||||||
id: 'qwen-turbo',
|
id: 'qwen-turbo',
|
||||||
provider: 'dashscope',
|
provider: 'dashscope',
|
||||||
@@ -609,6 +778,98 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
group: 'Llama3'
|
group: 'Llama3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
fireworks: [
|
||||||
|
{
|
||||||
|
id: 'accounts/fireworks/models/mythomax-l2-13b',
|
||||||
|
provider: 'fireworks',
|
||||||
|
name: 'mythomax-l2-13b',
|
||||||
|
group: 'Gryphe'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'accounts/fireworks/models/llama-v3-70b-instruct',
|
||||||
|
provider: 'fireworks',
|
||||||
|
name: 'Llama-3-70B-Instruct',
|
||||||
|
group: 'Llama3'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
zhinao: [
|
||||||
|
{
|
||||||
|
id: '360gpt-pro',
|
||||||
|
provider: 'zhinao',
|
||||||
|
name: '360gpt-pro',
|
||||||
|
group: '360Gpt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '360gpt-turbo',
|
||||||
|
provider: 'zhinao',
|
||||||
|
name: '360gpt-turbo',
|
||||||
|
group: '360Gpt'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hunyuan: [
|
||||||
|
{
|
||||||
|
id: 'hunyuan-pro',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-pro',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-standard',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-standard',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-lite',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-lite',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-standard-256k',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-standard-256k',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-vision',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-vision',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-code',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-code',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-role',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-role',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hunyuan-turbo',
|
||||||
|
provider: 'hunyuan',
|
||||||
|
name: 'hunyuan-turbo',
|
||||||
|
group: 'Hunyuan'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
nvidia: [
|
||||||
|
{
|
||||||
|
id: '01-ai/yi-large',
|
||||||
|
provider: 'nvidia',
|
||||||
|
name: 'yi-large',
|
||||||
|
group: 'Yi'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meta/llama-3.1-405b-instruct',
|
||||||
|
provider: 'nvidia',
|
||||||
|
name: 'llama-3.1-405b-instruct',
|
||||||
|
group: 'llama-3.1'
|
||||||
|
}
|
||||||
|
],
|
||||||
openrouter: [
|
openrouter: [
|
||||||
{
|
{
|
||||||
id: 'google/gemma-2-9b-it:free',
|
id: 'google/gemma-2-9b-it:free',
|
||||||
|
|||||||
48
src/renderer/src/config/prompts.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
export const AGENT_PROMPT = `
|
||||||
|
你是一个 Prompt 生成器。你会将用户输入的信息整合成一个 Markdown 语法的结构化的 Prompt。请务必不要使用代码块输出,而是直接显示!
|
||||||
|
|
||||||
|
## Role :
|
||||||
|
[请填写你想定义的角色名称]
|
||||||
|
|
||||||
|
## Background :
|
||||||
|
[请描述角色的背景信息,例如其历史、来源或特定的知识背景]
|
||||||
|
|
||||||
|
## Preferences :
|
||||||
|
[请描述角色的偏好或特定风格,例如对某种设计或文化的偏好]
|
||||||
|
|
||||||
|
## Profile :
|
||||||
|
- version: 0.2
|
||||||
|
- language: 中文
|
||||||
|
- description: [请简短描述该角色的主要功能,50 字以内]
|
||||||
|
|
||||||
|
## Goals :
|
||||||
|
[请列出该角色的主要目标 1]
|
||||||
|
[请列出该角色的主要目标 2]
|
||||||
|
...
|
||||||
|
|
||||||
|
## Constrains :
|
||||||
|
[请列出该角色在互动中必须遵循的限制条件 1]
|
||||||
|
[请列出该角色在互动中必须遵循的限制条件 2]
|
||||||
|
...
|
||||||
|
|
||||||
|
## Skills :
|
||||||
|
[为了在限制条件下实现目标,该角色需要拥有的技能 1]
|
||||||
|
[为了在限制条件下实现目标,该角色需要拥有的技能 2]
|
||||||
|
...
|
||||||
|
|
||||||
|
## Examples :
|
||||||
|
[提供一个输出示例 1,展示角色的可能回答或行为]
|
||||||
|
[提供一个输出示例 2]
|
||||||
|
...
|
||||||
|
|
||||||
|
## OutputFormat :
|
||||||
|
[请描述该角色的工作流程的第一步]
|
||||||
|
[请描述该角色的工作流程的第二步]
|
||||||
|
...
|
||||||
|
|
||||||
|
## Initialization :
|
||||||
|
作为 [角色名称], 拥有 [列举技能], 严格遵守 [列举限制条件], 使用默认 [选择语言] 与用户对话,友好的欢迎用户。然后介绍自己,并提示用户输入.
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SUMMARIZE_PROMPT =
|
||||||
|
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||||
@@ -1,21 +1,27 @@
|
|||||||
|
import ZhinaoProviderLogo from '@renderer/assets/images/models/360.png'
|
||||||
|
import HunyuanProviderLogo from '@renderer/assets/images/models/hunyuan.png'
|
||||||
|
import AzureProviderLogo from '@renderer/assets/images/models/microsoft.png'
|
||||||
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
|
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg'
|
||||||
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png'
|
import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png'
|
||||||
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.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 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 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'
|
import GithubProviderLogo from '@renderer/assets/images/providers/github.png'
|
||||||
import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
|
import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
|
||||||
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
|
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
|
||||||
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
|
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
|
||||||
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
|
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
|
||||||
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png'
|
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png'
|
||||||
|
import NvidiaProviderLogo from '@renderer/assets/images/providers/nvidia.png'
|
||||||
import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png'
|
import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png'
|
||||||
import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png'
|
import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png'
|
||||||
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png'
|
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png'
|
||||||
import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png'
|
import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png'
|
||||||
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
||||||
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
||||||
|
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
||||||
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
|
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
|
||||||
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
||||||
|
|
||||||
@@ -42,7 +48,7 @@ export function getProviderLogo(providerId: string) {
|
|||||||
case 'baichuan':
|
case 'baichuan':
|
||||||
return BaichuanProviderLogo
|
return BaichuanProviderLogo
|
||||||
case 'dashscope':
|
case 'dashscope':
|
||||||
return DashScopeProviderLogo
|
return BailianProviderLogo
|
||||||
case 'anthropic':
|
case 'anthropic':
|
||||||
return AnthropicProviderLogo
|
return AnthropicProviderLogo
|
||||||
case 'aihubmix':
|
case 'aihubmix':
|
||||||
@@ -61,6 +67,18 @@ export function getProviderLogo(providerId: string) {
|
|||||||
return GithubProviderLogo
|
return GithubProviderLogo
|
||||||
case 'ocoolai':
|
case 'ocoolai':
|
||||||
return OcoolAiProviderLogo
|
return OcoolAiProviderLogo
|
||||||
|
case 'together':
|
||||||
|
return TogetherProviderLogo
|
||||||
|
case 'fireworks':
|
||||||
|
return FireworksProviderLogo
|
||||||
|
case 'zhinao':
|
||||||
|
return ZhinaoProviderLogo
|
||||||
|
case 'nvidia':
|
||||||
|
return NvidiaProviderLogo
|
||||||
|
case 'azure-openai':
|
||||||
|
return AzureProviderLogo
|
||||||
|
case 'hunyuan':
|
||||||
|
return HunyuanProviderLogo
|
||||||
default:
|
default:
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -122,6 +140,17 @@ export const PROVIDER_CONFIG = {
|
|||||||
models: 'https://docs.ooo.cool/guides/jiage/'
|
models: 'https://docs.ooo.cool/guides/jiage/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
together: {
|
||||||
|
api: {
|
||||||
|
url: 'https://api.tohgether.xyz'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://www.together.ai/',
|
||||||
|
apiKey: 'https://api.together.ai/settings/api-keys',
|
||||||
|
docs: 'https://docs.together.ai/docs/introduction',
|
||||||
|
models: 'https://docs.together.ai/docs/chat-models'
|
||||||
|
}
|
||||||
|
},
|
||||||
github: {
|
github: {
|
||||||
api: {
|
api: {
|
||||||
url: 'https://models.inference.ai.azure.com/'
|
url: 'https://models.inference.ai.azure.com/'
|
||||||
@@ -182,10 +211,10 @@ export const PROVIDER_CONFIG = {
|
|||||||
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
|
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
|
||||||
},
|
},
|
||||||
websites: {
|
websites: {
|
||||||
official: 'https://dashscope.aliyun.com/',
|
official: 'https://www.aliyun.com/product/bailian',
|
||||||
apiKey: 'https://help.aliyun.com/zh/dashscope/developer-reference/acquisition-and-configuration-of-api-key',
|
apiKey: 'https://bailian.console.aliyun.com/?apiKey=1#/api-key',
|
||||||
docs: 'https://help.aliyun.com/zh/dashscope/',
|
docs: 'https://help.aliyun.com/zh/model-studio/getting-started/',
|
||||||
models: 'https://dashscope.console.aliyun.com/model'
|
models: 'https://bailian.console.aliyun.com/model-market#/model-market'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stepfun: {
|
stepfun: {
|
||||||
@@ -279,5 +308,60 @@ export const PROVIDER_CONFIG = {
|
|||||||
docs: 'https://doc.aihubmix.com/',
|
docs: 'https://doc.aihubmix.com/',
|
||||||
models: 'https://aihubmix.com/models'
|
models: 'https://aihubmix.com/models'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
fireworks: {
|
||||||
|
api: {
|
||||||
|
url: 'https://api.fireworks.ai/inference'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://fireworks.ai/',
|
||||||
|
apiKey: 'https://fireworks.ai/account/api-keys',
|
||||||
|
docs: 'https://docs.fireworks.ai/getting-started/introduction',
|
||||||
|
models: 'https://fireworks.ai/dashboard/models'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zhinao: {
|
||||||
|
api: {
|
||||||
|
url: 'https://api.360.cn'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://ai.360.com/',
|
||||||
|
apiKey: 'https://ai.360.com/platform/keys',
|
||||||
|
docs: 'https://ai.360.com/platform/docs/overview',
|
||||||
|
models: 'https://ai.360.com/platform/limit'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hunyuan: {
|
||||||
|
api: {
|
||||||
|
url: 'https://api.hunyuan.cloud.tencent.com'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://cloud.tencent.com/product/hunyuan',
|
||||||
|
apiKey: 'https://console.cloud.tencent.com/hunyuan/api-key',
|
||||||
|
docs: 'https://cloud.tencent.com/document/product/1729/111007',
|
||||||
|
models: 'https://cloud.tencent.com/document/product/1729/104753'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nvidia: {
|
||||||
|
api: {
|
||||||
|
url: 'https://integrate.api.nvidia.com'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://ai.360.com/',
|
||||||
|
apiKey: 'https://build.nvidia.com/meta/llama-3_1-405b-instruct',
|
||||||
|
docs: 'https://docs.api.nvidia.com/nim/reference/llm-apis',
|
||||||
|
models: 'https://build.nvidia.com/nim'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'azure-openai': {
|
||||||
|
api: {
|
||||||
|
url: ''
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://azure.microsoft.com/en-us/products/ai-services/openai-service',
|
||||||
|
apiKey: 'https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/OpenAI',
|
||||||
|
docs: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/',
|
||||||
|
models: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
Segmented: {
|
Segmented: {
|
||||||
trackBg: 'transparent',
|
trackBg: 'transparent',
|
||||||
itemSelectedBg: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
itemSelectedBg: isDarkTheme ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
||||||
boxShadowTertiary: undefined
|
boxShadowTertiary: undefined,
|
||||||
|
borderRadiusLG: 12,
|
||||||
|
borderRadiusSM: 12,
|
||||||
|
borderRadiusXS: 12
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
activeBarBorderWidth: 0,
|
activeBarBorderWidth: 0,
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import { RootState } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { addAgent, removeAgent, updateAgent, updateAgents } from '@renderer/store/agents'
|
import { addAgent, removeAgent, updateAgent, updateAgents, updateAgentSettings } from '@renderer/store/agents'
|
||||||
import { Agent } from '@renderer/types'
|
import { Agent, AssistantSettings } from '@renderer/types'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
export function useAgents() {
|
export function useAgents() {
|
||||||
const agents = useSelector((state: RootState) => state.agents.agents)
|
const agents = useAppSelector((state) => state.agents.agents)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agents,
|
agents,
|
||||||
|
updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents)),
|
||||||
addAgent: (agent: Agent) => dispatch(addAgent(agent)),
|
addAgent: (agent: Agent) => dispatch(addAgent(agent)),
|
||||||
removeAgent: (agent: Agent) => dispatch(removeAgent(agent)),
|
removeAgent: (id: string) => dispatch(removeAgent({ id }))
|
||||||
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
|
}
|
||||||
updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents))
|
}
|
||||||
|
|
||||||
|
export function useAgent(id: string) {
|
||||||
|
const agent = useAppSelector((state) => state.agents.agents.find((a) => a.id === id) as Agent)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return {
|
||||||
|
agent,
|
||||||
|
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
|
||||||
|
updateAgentSettings: (settings: Partial<AssistantSettings>) => {
|
||||||
|
dispatch(updateAgentSettings({ assistantId: agent.id, settings }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function useAssistant(id: string) {
|
|||||||
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
|
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
|
||||||
setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
|
setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
|
||||||
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
|
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
|
||||||
updateAssistantSettings: (settings: AssistantSettings) => {
|
updateAssistantSettings: (settings: Partial<AssistantSettings>) => {
|
||||||
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
|
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ export function useDefaultAssistant() {
|
|||||||
return {
|
return {
|
||||||
defaultAssistant: {
|
defaultAssistant: {
|
||||||
...defaultAssistant,
|
...defaultAssistant,
|
||||||
topics: [getDefaultTopic()]
|
topics: [getDefaultTopic(defaultAssistant.id)]
|
||||||
},
|
},
|
||||||
updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
|
updateDefaultAssistant: (assistant: Assistant) => dispatch(updateDefaultAssistant({ assistant }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function useProviders() {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
providers,
|
providers: providers || {},
|
||||||
addProvider: (provider: Provider) => dispatch(addProvider(provider)),
|
addProvider: (provider: Provider) => dispatch(addProvider(provider)),
|
||||||
removeProvider: (provider: Provider) => dispatch(removeProvider(provider)),
|
removeProvider: (provider: Provider) => dispatch(removeProvider(provider)),
|
||||||
updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
|
updateProvider: (provider: Provider) => dispatch(updateProvider(provider)),
|
||||||
|
|||||||
20
src/renderer/src/hooks/useScrollPosition.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { throttle } from 'lodash'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export default function useScrollPosition(key: string) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const scrollKey = `scroll:${key}`
|
||||||
|
|
||||||
|
const handleScroll = throttle(() => {
|
||||||
|
const position = containerRef.current?.scrollTop ?? 0
|
||||||
|
window.keyv.set(scrollKey, position)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const scroll = () => containerRef.current?.scrollTo({ top: window.keyv.get(scrollKey) || 0 })
|
||||||
|
scroll()
|
||||||
|
setTimeout(scroll, 50)
|
||||||
|
}, [scrollKey])
|
||||||
|
|
||||||
|
return { containerRef, handleScroll }
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { deleteMessageFiles } from '@renderer/services/messages'
|
import { deleteMessageFiles } from '@renderer/services/messages'
|
||||||
|
import store from '@renderer/store'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { find } from 'lodash'
|
import { find } from 'lodash'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@@ -8,9 +9,9 @@ import { useAssistant } from './useAssistant'
|
|||||||
|
|
||||||
let _activeTopic: Topic
|
let _activeTopic: Topic
|
||||||
|
|
||||||
export function useActiveTopic(_assistant: Assistant) {
|
export function useActiveTopic(_assistant: Assistant, topic?: Topic) {
|
||||||
const { assistant } = useAssistant(_assistant.id)
|
const { assistant } = useAssistant(_assistant.id)
|
||||||
const [activeTopic, setActiveTopic] = useState(_activeTopic || assistant?.topics[0])
|
const [activeTopic, setActiveTopic] = useState(topic || _activeTopic || assistant?.topics[0])
|
||||||
|
|
||||||
_activeTopic = activeTopic
|
_activeTopic = activeTopic
|
||||||
|
|
||||||
@@ -28,6 +29,14 @@ export function getTopic(assistant: Assistant, topicId: string) {
|
|||||||
return assistant?.topics.find((topic) => topic.id === topicId)
|
return assistant?.topics.find((topic) => topic.id === topicId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getTopicById(topicId: string) {
|
||||||
|
const assistants = store.getState().assistants.assistants
|
||||||
|
const topics = assistants.map((assistant) => assistant.topics).flat()
|
||||||
|
const topic = topics.find((topic) => topic.id === topicId)
|
||||||
|
const messages = await TopicManager.getTopicMessages(topicId)
|
||||||
|
return { ...topic, messages } as Topic
|
||||||
|
}
|
||||||
|
|
||||||
export class TopicManager {
|
export class TopicManager {
|
||||||
static async getTopic(id: string) {
|
static async getTopic(id: string) {
|
||||||
return await db.topics.get(id)
|
return await db.topics.get(id)
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
"default": "Default",
|
"default": "Default",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"chat": "Chat"
|
"chat": "Chat",
|
||||||
|
"close": "Close",
|
||||||
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
@@ -60,7 +62,8 @@
|
|||||||
"upgrade.success.title": "Upgrade successfully",
|
"upgrade.success.title": "Upgrade successfully",
|
||||||
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
||||||
"upgrade.success.button": "Restart",
|
"upgrade.success.button": "Restart",
|
||||||
"topic.added": "New topic added"
|
"topic.added": "New topic added",
|
||||||
|
"save.success.title": "Saved successfully"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -71,12 +74,12 @@
|
|||||||
"topics.auto_rename": "Auto Rename",
|
"topics.auto_rename": "Auto Rename",
|
||||||
"topics.edit.title": "Edit Name",
|
"topics.edit.title": "Edit Name",
|
||||||
"topics.edit.placeholder": "Enter new name",
|
"topics.edit.placeholder": "Enter new name",
|
||||||
"topics.delete.all.title": "Delete all topics",
|
"topics.clear.title": "Clear Messages",
|
||||||
"topics.delete.all.content": "Are you sure you want to delete all topics?",
|
|
||||||
"topics.move_to": "Move to",
|
"topics.move_to": "Move to",
|
||||||
"topics.list": "Topic List",
|
"topics.list": "Topic List",
|
||||||
"topics.export.title": "Export",
|
"topics.export.title": "Export",
|
||||||
"topics.export.image": "Export as image",
|
"topics.export.image": "Export as image",
|
||||||
|
"topics.export.md": "Export as markdown",
|
||||||
"input.new_topic": "New Topic",
|
"input.new_topic": "New Topic",
|
||||||
"input.topics": " Topics ",
|
"input.topics": " Topics ",
|
||||||
"input.clear": "Clear",
|
"input.clear": "Clear",
|
||||||
@@ -89,7 +92,7 @@
|
|||||||
"input.send": "Send",
|
"input.send": "Send",
|
||||||
"input.pause": "Pause",
|
"input.pause": "Pause",
|
||||||
"input.settings": "Settings",
|
"input.settings": "Settings",
|
||||||
"input.upload": "Upload image or text file",
|
"input.upload": "Upload image or document file",
|
||||||
"input.context_count.tip": "Context Count",
|
"input.context_count.tip": "Context Count",
|
||||||
"input.estimated_tokens.tip": "Estimated tokens",
|
"input.estimated_tokens.tip": "Estimated tokens",
|
||||||
"settings.temperature": "Temperature",
|
"settings.temperature": "Temperature",
|
||||||
@@ -101,21 +104,38 @@
|
|||||||
"settings.reset": "Reset",
|
"settings.reset": "Reset",
|
||||||
"settings.set_as_default": "Apply to default assistant",
|
"settings.set_as_default": "Apply to default assistant",
|
||||||
"settings.max": "Max",
|
"settings.max": "Max",
|
||||||
|
"settings.show_line_numbers": "Show Line Numbers in Code",
|
||||||
"suggestions.title": "Suggested Questions",
|
"suggestions.title": "Suggested Questions",
|
||||||
"add.assistant.title": "Add Assistant",
|
"add.assistant.title": "Add Assistant",
|
||||||
"message.new.context": "New Context",
|
"message.new.context": "New Context",
|
||||||
"message.new.branch": "New Branch",
|
"message.new.branch": "New Branch",
|
||||||
"assistant.search.placeholder": "Search"
|
"message.new.branch.created": "New Branch Created",
|
||||||
|
"assistant.search.placeholder": "Search",
|
||||||
|
"artifacts.button.preview": "Preview",
|
||||||
|
"artifacts.button.download": "Download"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "Assistants",
|
"title": "Assistants",
|
||||||
"abbr": "Assistant",
|
"abbr": "Assistant",
|
||||||
"search": "Search assistants...",
|
"search": "Search assistants...",
|
||||||
"prompt_settings": "Prompt Settings",
|
"settings.prompt": "Prompt Settings",
|
||||||
"model_settings": "Model Settings"
|
"settings.model": "Model Settings",
|
||||||
|
"settings.preset_messages": "Preset Messages",
|
||||||
|
"settings.default_model": "Default Model",
|
||||||
|
"settings.auto_reset_model": "Auto Reset Model",
|
||||||
|
"settings.auto_reset_model.tip": "Automatically reset the model when a new topic is created.",
|
||||||
|
"edit.title": "Edit Assistant",
|
||||||
|
"copy.title": "Copy Assistant",
|
||||||
|
"clear.title": "Clear topics",
|
||||||
|
"clear.content": "Clearing the topic will delete all topics and files in the assistant. Are you sure you want to continue?",
|
||||||
|
"save.title": "Save to agent",
|
||||||
|
"save.success": "Saved successfully",
|
||||||
|
"delete.title": "Delete Assistant",
|
||||||
|
"delete.content": "Deleting an assistant will delete all topics and files under the assistant. Are you sure you want to delete it?"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"stream_output": "Stream Output"
|
"stream_output": "Stream Output",
|
||||||
|
"search": "Search models..."
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Files",
|
"title": "Files",
|
||||||
@@ -128,20 +148,45 @@
|
|||||||
"agents": {
|
"agents": {
|
||||||
"title": "Agents",
|
"title": "Agents",
|
||||||
"my_agents": "My Agents",
|
"my_agents": "My Agents",
|
||||||
"add.title": "Add Agent",
|
"add.title": "Create Agent",
|
||||||
"edit.title": "Edit Agent",
|
"edit.title": "Edit Agent",
|
||||||
"add.name": "Name",
|
"add.name": "Name",
|
||||||
"add.name.placeholder": "Enter name",
|
"add.name.placeholder": "Enter name",
|
||||||
"add.prompt": "Prompt",
|
"add.prompt": "Prompt",
|
||||||
"add.prompt.placeholder": "Enter prompt",
|
"add.prompt.placeholder": "Enter prompt",
|
||||||
"add.button": "Add",
|
"add.button": "Add to Assistant",
|
||||||
"manage.title": "Manage Agents",
|
"manage.title": "Manage Agents",
|
||||||
"delete.popup.content": "Are you sure you want to delete this agent?",
|
"delete.popup.content": "Are you sure you want to delete this agent?",
|
||||||
"tag.default": "Default",
|
"tag.default": "Default",
|
||||||
"tag.system": "System",
|
"tag.system": "System",
|
||||||
"tag.user": "Mine"
|
"tag.agent": "Agent",
|
||||||
|
"edit.message.title": "Preset messages",
|
||||||
|
"edit.message.add.title": "Add",
|
||||||
|
"edit.message.group.title": "Message Group",
|
||||||
|
"edit.message.assistant.title": "Assistant",
|
||||||
|
"edit.message.assistant.placeholder": "Enter assistant message",
|
||||||
|
"edit.message.user.title": "User",
|
||||||
|
"edit.message.user.placeholder": "Enter user message",
|
||||||
|
"edit.message.empty.content": "Conversation input content cannot be empty",
|
||||||
|
"edit.model.select.title": "Select Model",
|
||||||
|
"edit.settings.hide_preset_messages": "Hide Preset Message"
|
||||||
|
},
|
||||||
|
"minapp": {
|
||||||
|
"title": "MinApp"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"title": "Topics Search",
|
||||||
|
"search.placeholder": "Search topics or messages...",
|
||||||
|
"continue_chat": "Continue Chatting",
|
||||||
|
"search.topics.empty": "No topics found, press Enter to search all messages",
|
||||||
|
"locate.message": "Locate the message"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
|
"nvidia": "Nvidia",
|
||||||
|
"hunyuan": "Tencent Hunyuan",
|
||||||
|
"zhinao": "360AI",
|
||||||
|
"fireworks": "Fireworks",
|
||||||
|
"together": "Together",
|
||||||
"openai": "OpenAI",
|
"openai": "OpenAI",
|
||||||
"gemini": "Gemini",
|
"gemini": "Gemini",
|
||||||
"deepseek": "DeepSeek",
|
"deepseek": "DeepSeek",
|
||||||
@@ -153,7 +198,7 @@
|
|||||||
"groq": "Groq",
|
"groq": "Groq",
|
||||||
"ollama": "Ollama",
|
"ollama": "Ollama",
|
||||||
"baichuan": "Baichuan",
|
"baichuan": "Baichuan",
|
||||||
"dashscope": "DashScope",
|
"dashscope": "Alibaba Cloud",
|
||||||
"anthropic": "Anthropic",
|
"anthropic": "Anthropic",
|
||||||
"aihubmix": "AiHubMix",
|
"aihubmix": "AiHubMix",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
@@ -161,11 +206,13 @@
|
|||||||
"minimax": "MiniMax",
|
"minimax": "MiniMax",
|
||||||
"graphrag-kylin-mountain": "GraphRAG",
|
"graphrag-kylin-mountain": "GraphRAG",
|
||||||
"github": "GitHub Models",
|
"github": "GitHub Models",
|
||||||
"ocoolai": "ocoolAI"
|
"ocoolai": "ocoolAI",
|
||||||
|
"azure-openai": "Azure OpenAI"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"general": "General Settings",
|
"general": "General Settings",
|
||||||
|
"data": "Data Settings",
|
||||||
"provider": "Model Provider",
|
"provider": "Model Provider",
|
||||||
"model": "Default Model",
|
"model": "Default Model",
|
||||||
"assistant": "Default Assistant",
|
"assistant": "Default Assistant",
|
||||||
@@ -179,6 +226,7 @@
|
|||||||
"messages.input.send_shortcuts": "Send shortcuts",
|
"messages.input.send_shortcuts": "Send shortcuts",
|
||||||
"messages.input.paste_long_text_as_file": "Paste long text as file",
|
"messages.input.paste_long_text_as_file": "Paste long text as file",
|
||||||
"messages.markdown_rendering_input_message": "Markdown render input msg",
|
"messages.markdown_rendering_input_message": "Markdown render input msg",
|
||||||
|
"messages.math_engine": "Math render engine",
|
||||||
"general.title": "General Settings",
|
"general.title": "General Settings",
|
||||||
"general.user_name": "User Name",
|
"general.user_name": "User Name",
|
||||||
"general.user_name.placeholder": "Enter your name",
|
"general.user_name.placeholder": "Enter your name",
|
||||||
@@ -186,30 +234,31 @@
|
|||||||
"general.backup.button": "Backup",
|
"general.backup.button": "Backup",
|
||||||
"general.restore.button": "Restore",
|
"general.restore.button": "Restore",
|
||||||
"general.view_webdav_settings": "View WebDAV settings",
|
"general.view_webdav_settings": "View WebDAV settings",
|
||||||
"general.webdav.title": "WebDAV",
|
|
||||||
"general.webdav.host": "WebDAV Host",
|
|
||||||
"general.webdav.host.placeholder": "http://localhost:8080",
|
|
||||||
"general.webdav.user": "WebDAV User",
|
|
||||||
"general.webdav.password": "WebDAV Password",
|
|
||||||
"general.webdav.path": "WebDAV Path",
|
|
||||||
"general.webdav.path.placeholder": "/backup",
|
|
||||||
"general.webdav.backup.button": "Backup to WebDAV",
|
|
||||||
"general.webdav.restore.button": "Restore from WebDAV",
|
|
||||||
"general.reset.title": "Data Reset",
|
"general.reset.title": "Data Reset",
|
||||||
"general.reset.button": "Reset",
|
"general.reset.button": "Reset",
|
||||||
"general.check_update_setting": "Check for updates",
|
"general.manually_check_update.title": "Turn off update checking",
|
||||||
"general.manual_update_check": "Check for updates manually",
|
"data.webdav.title": "WebDAV",
|
||||||
"general.auto_update_check": "Check for updates automatically",
|
"data.webdav.host": "WebDAV Host",
|
||||||
|
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||||
|
"data.webdav.user": "WebDAV User",
|
||||||
|
"data.webdav.password": "WebDAV Password",
|
||||||
|
"data.webdav.path": "WebDAV Path",
|
||||||
|
"data.webdav.path.placeholder": "/backup",
|
||||||
|
"data.webdav.backup.button": "Backup to WebDAV",
|
||||||
|
"data.webdav.restore.button": "Restore from WebDAV",
|
||||||
"advanced.title": "Advanced Settings",
|
"advanced.title": "Advanced Settings",
|
||||||
"advanced.click_assistant_switch_to_topics": "Auto switch to topic",
|
"advanced.click_assistant_switch_to_topics": "Auto switch to topic",
|
||||||
"provider.api_key": "API Key",
|
"provider.api_key": "API Key",
|
||||||
"provider.check": "Check",
|
"provider.check": "Check",
|
||||||
"provider.get_api_key": "Get API Key",
|
"provider.get_api_key": "Get API Key",
|
||||||
"provider.api_host": "API Host",
|
"provider.api_host": "API Host",
|
||||||
|
"provider.api_version": "API Version",
|
||||||
"provider.docs_check": "Check",
|
"provider.docs_check": "Check",
|
||||||
"provider.docs_more_details": "for more details",
|
"provider.docs_more_details": "for more details",
|
||||||
"provider.search_placeholder": "Search model id or name",
|
"provider.search_placeholder": "Search model id or name",
|
||||||
"provider.api.url.reset": "Reset",
|
"provider.api.url.reset": "Reset",
|
||||||
|
"provider.api.url.preview": "Preview: {{url}}",
|
||||||
|
"provider.api.url.tip": "Ending with / ignores v1, ending with # forces use of input address",
|
||||||
"models.default_assistant_model": "Default Assistant Model",
|
"models.default_assistant_model": "Default Assistant Model",
|
||||||
"models.topic_naming_model": "Topic Naming Model",
|
"models.topic_naming_model": "Topic Naming Model",
|
||||||
"models.translate_model": "Translate Model",
|
"models.translate_model": "Translate Model",
|
||||||
@@ -257,7 +306,8 @@
|
|||||||
"font_size.title": "Message Font Size",
|
"font_size.title": "Message Font Size",
|
||||||
"topic.position": "Topic Position",
|
"topic.position": "Topic Position",
|
||||||
"topic.position.left": "Left",
|
"topic.position.left": "Left",
|
||||||
"topic.position.right": "Right"
|
"topic.position.right": "Right",
|
||||||
|
"topic.show.time": "Show Topic Time"
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
"title": "Translation",
|
"title": "Translation",
|
||||||
@@ -286,9 +336,6 @@
|
|||||||
"keep_alive_time.placeholder": "Minutes",
|
"keep_alive_time.placeholder": "Minutes",
|
||||||
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes."
|
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes."
|
||||||
},
|
},
|
||||||
"minapp": {
|
|
||||||
"title": "MinApp"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"chat.response": "Something went wrong. Please check if you have set your API key in the Settings > Providers",
|
"chat.response": "Something went wrong. Please check if you have set your API key in the Settings > Providers",
|
||||||
"backup.file_format": "Backup file format error"
|
"backup.file_format": "Backup file format error"
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
"default": "默认",
|
"default": "默认",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
"back": "返回",
|
"back": "返回",
|
||||||
"chat": "聊天"
|
"chat": "聊天",
|
||||||
|
"close": "关闭",
|
||||||
|
"cancel": "取消"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
@@ -60,7 +62,8 @@
|
|||||||
"upgrade.success.title": "升级成功",
|
"upgrade.success.title": "升级成功",
|
||||||
"upgrade.success.content": "重启应用以完成升级",
|
"upgrade.success.content": "重启应用以完成升级",
|
||||||
"upgrade.success.button": "重启",
|
"upgrade.success.button": "重启",
|
||||||
"topic.added": "话题添加成功"
|
"topic.added": "话题添加成功",
|
||||||
|
"save.success.title": "保存成功"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -71,25 +74,25 @@
|
|||||||
"topics.auto_rename": "生成话题名",
|
"topics.auto_rename": "生成话题名",
|
||||||
"topics.edit.title": "编辑话题名",
|
"topics.edit.title": "编辑话题名",
|
||||||
"topics.edit.placeholder": "输入新名称",
|
"topics.edit.placeholder": "输入新名称",
|
||||||
"topics.delete.all.title": "删除所有话题",
|
"topics.clear.title": "清空消息",
|
||||||
"topics.delete.all.content": "确定要删除所有话题吗?",
|
|
||||||
"topics.move_to": "移动到",
|
"topics.move_to": "移动到",
|
||||||
"topics.list": "话题列表",
|
"topics.list": "话题列表",
|
||||||
"topics.export.title": "导出",
|
"topics.export.title": "导出",
|
||||||
"topics.export.image": "导出为图片",
|
"topics.export.image": "导出为图片",
|
||||||
|
"topics.export.md": "导出为 Markdown",
|
||||||
"input.new_topic": "新话题",
|
"input.new_topic": "新话题",
|
||||||
"input.topics": " 话题 ",
|
"input.topics": " 话题 ",
|
||||||
"input.clear": "清除会话消息",
|
"input.clear": "清空消息",
|
||||||
"input.new.context": "清除上下文",
|
"input.new.context": "清除上下文",
|
||||||
"input.expand": "展开",
|
"input.expand": "展开",
|
||||||
"input.collapse": "收起",
|
"input.collapse": "收起",
|
||||||
"input.clear.title": "清除消息?",
|
"input.clear.title": "清空消息",
|
||||||
"input.clear.content": "确定要清除当前会话所有消息吗?",
|
"input.clear.content": "确定要清除当前会话所有消息吗?",
|
||||||
"input.placeholder": "在这里输入消息...",
|
"input.placeholder": "在这里输入消息...",
|
||||||
"input.send": "发送",
|
"input.send": "发送",
|
||||||
"input.pause": "暂停",
|
"input.pause": "暂停",
|
||||||
"input.settings": "设置",
|
"input.settings": "设置",
|
||||||
"input.upload": "上传图片或纯文本文件",
|
"input.upload": "上传图片或文档",
|
||||||
"input.context_count.tip": "上下文数",
|
"input.context_count.tip": "上下文数",
|
||||||
"input.estimated_tokens.tip": "预估 token 数",
|
"input.estimated_tokens.tip": "预估 token 数",
|
||||||
"settings.temperature": "模型温度",
|
"settings.temperature": "模型温度",
|
||||||
@@ -101,21 +104,38 @@
|
|||||||
"settings.reset": "重置",
|
"settings.reset": "重置",
|
||||||
"settings.set_as_default": "应用到默认助手",
|
"settings.set_as_default": "应用到默认助手",
|
||||||
"settings.max": "不限",
|
"settings.max": "不限",
|
||||||
|
"settings.show_line_numbers": "代码显示行号",
|
||||||
"suggestions.title": "建议的问题",
|
"suggestions.title": "建议的问题",
|
||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "清除上下文",
|
"message.new.context": "清除上下文",
|
||||||
"message.new.branch": "新分支",
|
"message.new.branch": "新分支",
|
||||||
"assistant.search.placeholder": "搜索"
|
"message.new.branch.created": "新分支已创建",
|
||||||
|
"assistant.search.placeholder": "搜索",
|
||||||
|
"artifacts.button.preview": "预览",
|
||||||
|
"artifacts.button.download": "下载"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "助手",
|
"title": "助手",
|
||||||
"abbr": "助手",
|
"abbr": "助手",
|
||||||
"search": "搜索助手",
|
"search": "搜索助手",
|
||||||
"prompt_settings": "提示词设置",
|
"settings.prompt": "提示词设置",
|
||||||
"model_settings": "模型设置"
|
"settings.model": "模型设置",
|
||||||
|
"settings.preset_messages": "预设消息",
|
||||||
|
"settings.default_model": "默认模型",
|
||||||
|
"settings.auto_reset_model": "自动重置模型",
|
||||||
|
"settings.auto_reset_model.tip": "创建新话题时自动重置模型",
|
||||||
|
"edit.title": "编辑助手",
|
||||||
|
"copy.title": "复制助手",
|
||||||
|
"clear.title": "清空话题",
|
||||||
|
"clear.content": "清空话题会删除助手下所有话题和文件,确定要继续吗?",
|
||||||
|
"save.title": "保存到智能体",
|
||||||
|
"save.success": "保存成功",
|
||||||
|
"delete.title": "删除助手",
|
||||||
|
"delete.content": "删除助手会删除所有该助手下的话题和文件,确定要继续吗?"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"stream_output": "流式输出"
|
"stream_output": "流式输出",
|
||||||
|
"search": "搜索模型..."
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"title": "文件",
|
"title": "文件",
|
||||||
@@ -128,20 +148,45 @@
|
|||||||
"agents": {
|
"agents": {
|
||||||
"title": "智能体",
|
"title": "智能体",
|
||||||
"my_agents": "我的智能体",
|
"my_agents": "我的智能体",
|
||||||
"add.title": "添加智能体",
|
"add.title": "创建智能体",
|
||||||
"edit.title": "编辑智能体",
|
"edit.title": "编辑智能体",
|
||||||
"add.name": "名称",
|
"add.name": "名称",
|
||||||
"add.name.placeholder": "输入名称",
|
"add.name.placeholder": "输入名称",
|
||||||
"add.prompt": "提示词",
|
"add.prompt": "提示词",
|
||||||
"add.prompt.placeholder": "输入提示词",
|
"add.prompt.placeholder": "输入提示词",
|
||||||
"add.button": "添加",
|
"add.button": "添加到助手",
|
||||||
"manage.title": "管理智能体",
|
"manage.title": "管理智能体",
|
||||||
"delete.popup.content": "确定要删除此智能体吗?",
|
"delete.popup.content": "确定要删除此智能体吗?",
|
||||||
"tag.default": "默认",
|
"tag.default": "默认",
|
||||||
"tag.system": "系统",
|
"tag.system": "系统",
|
||||||
"tag.user": "我的"
|
"tag.agent": "智能体",
|
||||||
|
"edit.message.title": "预设消息",
|
||||||
|
"edit.message.add.title": "添加",
|
||||||
|
"edit.message.group.title": "消息组",
|
||||||
|
"edit.message.assistant.title": "助手",
|
||||||
|
"edit.message.assistant.placeholder": "输入助手消息",
|
||||||
|
"edit.message.user.title": "用户",
|
||||||
|
"edit.message.user.placeholder": "输入用户消息",
|
||||||
|
"edit.message.empty.content": "会话输入内容不能为空",
|
||||||
|
"edit.model.select.title": "选择模型",
|
||||||
|
"edit.settings.hide_preset_messages": "隐藏预设消息"
|
||||||
|
},
|
||||||
|
"minapp": {
|
||||||
|
"title": "小程序"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"title": "话题搜索",
|
||||||
|
"search.placeholder": "搜索话题或消息...",
|
||||||
|
"continue_chat": "继续聊天",
|
||||||
|
"search.topics.empty": "没有找到相关话题, 点击回车键搜索所有消息",
|
||||||
|
"locate.message": "定位到消息"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
|
"nvidia": "英伟达",
|
||||||
|
"hunyuan": "腾讯混元",
|
||||||
|
"zhinao": "360智脑",
|
||||||
|
"fireworks": "Fireworks",
|
||||||
|
"together": "Together",
|
||||||
"openai": "OpenAI",
|
"openai": "OpenAI",
|
||||||
"gemini": "Gemini",
|
"gemini": "Gemini",
|
||||||
"deepseek": "深度求索",
|
"deepseek": "深度求索",
|
||||||
@@ -153,7 +198,7 @@
|
|||||||
"groq": "Groq",
|
"groq": "Groq",
|
||||||
"ollama": "Ollama",
|
"ollama": "Ollama",
|
||||||
"baichuan": "百川",
|
"baichuan": "百川",
|
||||||
"dashscope": "阿里云灵积",
|
"dashscope": "阿里云百炼",
|
||||||
"anthropic": "Anthropic",
|
"anthropic": "Anthropic",
|
||||||
"aihubmix": "AiHubMix",
|
"aihubmix": "AiHubMix",
|
||||||
"stepfun": "阶跃星辰",
|
"stepfun": "阶跃星辰",
|
||||||
@@ -161,11 +206,13 @@
|
|||||||
"minimax": "MiniMax",
|
"minimax": "MiniMax",
|
||||||
"graphrag-kylin-mountain": "GraphRAG",
|
"graphrag-kylin-mountain": "GraphRAG",
|
||||||
"github": "GitHub Models",
|
"github": "GitHub Models",
|
||||||
"ocoolai": "ocoolAI"
|
"ocoolai": "ocoolAI",
|
||||||
|
"azure-openai": "Azure OpenAI"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "设置",
|
"title": "设置",
|
||||||
"general": "常规设置",
|
"general": "常规设置",
|
||||||
|
"data": "数据设置",
|
||||||
"provider": "模型服务",
|
"provider": "模型服务",
|
||||||
"model": "默认模型",
|
"model": "默认模型",
|
||||||
"assistant": "默认助手",
|
"assistant": "默认助手",
|
||||||
@@ -179,6 +226,7 @@
|
|||||||
"messages.input.send_shortcuts": "发送快捷键",
|
"messages.input.send_shortcuts": "发送快捷键",
|
||||||
"messages.input.paste_long_text_as_file": "长文本粘贴为文件",
|
"messages.input.paste_long_text_as_file": "长文本粘贴为文件",
|
||||||
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
||||||
|
"messages.math_engine": "数学公式引擎",
|
||||||
"general.title": "常规设置",
|
"general.title": "常规设置",
|
||||||
"general.user_name": "用户名",
|
"general.user_name": "用户名",
|
||||||
"general.user_name.placeholder": "请输入用户名",
|
"general.user_name.placeholder": "请输入用户名",
|
||||||
@@ -188,28 +236,29 @@
|
|||||||
"general.reset.title": "重置数据",
|
"general.reset.title": "重置数据",
|
||||||
"general.reset.button": "重置",
|
"general.reset.button": "重置",
|
||||||
"general.view_webdav_settings": "查看 WebDAV 设置",
|
"general.view_webdav_settings": "查看 WebDAV 设置",
|
||||||
"general.webdav.title": "WebDAV",
|
"general.manually_check_update.title": "关闭更新检测",
|
||||||
"general.webdav.host": "WebDAV 地址",
|
"data.webdav.title": "WebDAV",
|
||||||
"general.webdav.host.placeholder": "http://localhost:8080",
|
"data.webdav.host": "WebDAV 地址",
|
||||||
"general.webdav.user": "WebDAV 用户名",
|
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||||
"general.webdav.password": "WebDAV 密码",
|
"data.webdav.user": "WebDAV 用户名",
|
||||||
"general.webdav.path": "WebDAV 路径",
|
"data.webdav.password": "WebDAV 密码",
|
||||||
"general.webdav.path.placeholder": "/backup",
|
"data.webdav.path": "WebDAV 路径",
|
||||||
"general.webdav.backup.button": "备份到 WebDAV",
|
"data.webdav.path.placeholder": "/backup",
|
||||||
"general.webdav.restore.button": "从 WebDAV 恢复",
|
"data.webdav.backup.button": "备份到 WebDAV",
|
||||||
"general.check_update_setting": "更新设置",
|
"data.webdav.restore.button": "从 WebDAV 恢复",
|
||||||
"general.manual_update_check": "手动检查更新",
|
|
||||||
"general.auto_update_check": "自动检查更新",
|
|
||||||
"advanced.title": "高级设置",
|
"advanced.title": "高级设置",
|
||||||
"advanced.click_assistant_switch_to_topics": "点击助手切换到话题",
|
"advanced.click_assistant_switch_to_topics": "点击助手切换到话题",
|
||||||
"provider.api_key": "API 密钥",
|
"provider.api_key": "API 密钥",
|
||||||
"provider.check": "检查",
|
"provider.check": "检查",
|
||||||
"provider.get_api_key": "点击这里获取密钥",
|
"provider.get_api_key": "点击这里获取密钥",
|
||||||
"provider.api_host": "API 地址",
|
"provider.api_host": "API 地址",
|
||||||
|
"provider.api_version": "API 版本",
|
||||||
"provider.docs_check": "查看",
|
"provider.docs_check": "查看",
|
||||||
"provider.docs_more_details": "获取更多详情",
|
"provider.docs_more_details": "获取更多详情",
|
||||||
"provider.search_placeholder": "搜索模型 ID 或名称",
|
"provider.search_placeholder": "搜索模型 ID 或名称",
|
||||||
"provider.api.url.reset": "重置",
|
"provider.api.url.reset": "重置",
|
||||||
|
"provider.api.url.preview": "预览: {{url}}",
|
||||||
|
"provider.api.url.tip": "/结尾忽略v1版本,#结尾强制使用输入地址",
|
||||||
"models.default_assistant_model": "默认助手模型",
|
"models.default_assistant_model": "默认助手模型",
|
||||||
"models.topic_naming_model": "话题命名模型",
|
"models.topic_naming_model": "话题命名模型",
|
||||||
"models.translate_model": "翻译模型",
|
"models.translate_model": "翻译模型",
|
||||||
@@ -257,7 +306,8 @@
|
|||||||
"font_size.title": "消息字体大小",
|
"font_size.title": "消息字体大小",
|
||||||
"topic.position": "话题位置",
|
"topic.position": "话题位置",
|
||||||
"topic.position.left": "左侧",
|
"topic.position.left": "左侧",
|
||||||
"topic.position.right": "右侧"
|
"topic.position.right": "右侧",
|
||||||
|
"topic.show.time": "显示话题时间"
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
"title": "翻译",
|
"title": "翻译",
|
||||||
@@ -286,9 +336,6 @@
|
|||||||
"keep_alive_time.placeholder": "分钟",
|
"keep_alive_time.placeholder": "分钟",
|
||||||
"keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)"
|
"keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)"
|
||||||
},
|
},
|
||||||
"minapp": {
|
|
||||||
"title": "小程序"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"chat.response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥",
|
"chat.response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥",
|
||||||
"backup.file_format": "备份文件格式错误"
|
"backup.file_format": "备份文件格式错误"
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
"default": "預設",
|
"default": "預設",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
"back": "返回",
|
"back": "返回",
|
||||||
"chat": "聊天"
|
"chat": "聊天",
|
||||||
|
"close": "關閉",
|
||||||
|
"cancel": "取消"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
@@ -60,7 +62,8 @@
|
|||||||
"upgrade.success.title": "升級成功",
|
"upgrade.success.title": "升級成功",
|
||||||
"upgrade.success.content": "請重新啟動應用以完成升級",
|
"upgrade.success.content": "請重新啟動應用以完成升級",
|
||||||
"upgrade.success.button": "重新啟動",
|
"upgrade.success.button": "重新啟動",
|
||||||
"topic.added": "新話題已添加"
|
"topic.added": "新話題已添加",
|
||||||
|
"save.success.title": "保存成功"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -71,12 +74,12 @@
|
|||||||
"topics.auto_rename": "自動重新命名",
|
"topics.auto_rename": "自動重新命名",
|
||||||
"topics.edit.title": "編輯名稱",
|
"topics.edit.title": "編輯名稱",
|
||||||
"topics.edit.placeholder": "輸入新名稱",
|
"topics.edit.placeholder": "輸入新名稱",
|
||||||
"topics.delete.all.title": "刪除所有話題",
|
"topics.clear.title": "清空消息",
|
||||||
"topics.delete.all.content": "確定要刪除所有話題嗎?",
|
|
||||||
"topics.move_to": "移動到",
|
"topics.move_to": "移動到",
|
||||||
"topics.list": "話題列表",
|
"topics.list": "話題列表",
|
||||||
"topics.export.title": "匯出",
|
"topics.export.title": "匯出",
|
||||||
"topics.export.image": "匯出為圖片",
|
"topics.export.image": "匯出為圖片",
|
||||||
|
"topics.export.md": "匯出為 Markdown",
|
||||||
"input.new_topic": "新話題",
|
"input.new_topic": "新話題",
|
||||||
"input.topics": " 話題 ",
|
"input.topics": " 話題 ",
|
||||||
"input.clear": "清除",
|
"input.clear": "清除",
|
||||||
@@ -89,7 +92,7 @@
|
|||||||
"input.send": "發送",
|
"input.send": "發送",
|
||||||
"input.pause": "暫停",
|
"input.pause": "暫停",
|
||||||
"input.settings": "設定",
|
"input.settings": "設定",
|
||||||
"input.upload": "上傳圖片或文字檔",
|
"input.upload": "上傳圖片或文檔",
|
||||||
"input.context_count.tip": "上下文數量",
|
"input.context_count.tip": "上下文數量",
|
||||||
"input.estimated_tokens.tip": "預估 Token 數",
|
"input.estimated_tokens.tip": "預估 Token 數",
|
||||||
"settings.temperature": "溫度",
|
"settings.temperature": "溫度",
|
||||||
@@ -101,21 +104,38 @@
|
|||||||
"settings.reset": "重置",
|
"settings.reset": "重置",
|
||||||
"settings.set_as_default": "設為預設助手",
|
"settings.set_as_default": "設為預設助手",
|
||||||
"settings.max": "最大",
|
"settings.max": "最大",
|
||||||
|
"settings.show_line_numbers": "代码顯示行號",
|
||||||
"suggestions.title": "建議的問題",
|
"suggestions.title": "建議的問題",
|
||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "新上下文",
|
"message.new.context": "新上下文",
|
||||||
"message.new.branch": "新分支",
|
"message.new.branch": "新分支",
|
||||||
"assistant.search.placeholder": "搜尋"
|
"message.new.branch.created": "新分支已建立",
|
||||||
|
"assistant.search.placeholder": "搜尋",
|
||||||
|
"artifacts.button.preview": "預覽",
|
||||||
|
"artifacts.button.download": "下載"
|
||||||
},
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"title": "助手",
|
"title": "助手",
|
||||||
"abbr": "助",
|
"abbr": "助",
|
||||||
"search": "搜尋助手...",
|
"search": "搜尋助手...",
|
||||||
"prompt_settings": "提示詞設定",
|
"settings.prompt": "提示詞設定",
|
||||||
"model_settings": "模型設定"
|
"settings.model": "模型設定",
|
||||||
|
"settings.preset_messages": "預設訊息",
|
||||||
|
"settings.default_model": "預設模型",
|
||||||
|
"settings.auto_reset_model": "自動重置模型",
|
||||||
|
"settings.auto_reset_model.tip": "每次新的話題時自動重置模型",
|
||||||
|
"edit.title": "編輯助手",
|
||||||
|
"copy.title": "複製助手",
|
||||||
|
"clear.title": "清空話題",
|
||||||
|
"clear.content": "清空話題會刪除助手下所有主題和文件,確定要繼續嗎?",
|
||||||
|
"save.title": "儲存到智能體",
|
||||||
|
"save.success": "儲存成功",
|
||||||
|
"delete.title": "删除助手",
|
||||||
|
"delete.content": "删除助手会删除所有该助手下的话题和文件,确定要繼續吗?"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"stream_output": "串流輸出"
|
"stream_output": "串流輸出",
|
||||||
|
"search": "搜尋模型..."
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"title": "檔案",
|
"title": "檔案",
|
||||||
@@ -128,32 +148,57 @@
|
|||||||
"agents": {
|
"agents": {
|
||||||
"title": "智能體",
|
"title": "智能體",
|
||||||
"my_agents": "我的智能體",
|
"my_agents": "我的智能體",
|
||||||
"add.title": "添加智能體",
|
"add.title": "创建智能體",
|
||||||
"edit.title": "編輯智能體",
|
"edit.title": "編輯智能體",
|
||||||
"add.name": "名稱",
|
"add.name": "名稱",
|
||||||
"add.name.placeholder": "輸入名稱",
|
"add.name.placeholder": "輸入名稱",
|
||||||
"add.prompt": "提示詞",
|
"add.prompt": "提示詞",
|
||||||
"add.prompt.placeholder": "輸入提示詞",
|
"add.prompt.placeholder": "輸入提示詞",
|
||||||
"add.button": "添加",
|
"add.button": "添加到助手",
|
||||||
"manage.title": "管理智能體",
|
"manage.title": "管理智能體",
|
||||||
"delete.popup.content": "確定要刪除此智能體嗎?",
|
"delete.popup.content": "確定要刪除此智能體嗎?",
|
||||||
"tag.default": "預設",
|
"tag.default": "預設",
|
||||||
"tag.system": "系統",
|
"tag.system": "系統",
|
||||||
"tag.user": "我的"
|
"tag.agent": "智能体",
|
||||||
|
"edit.message.title": "預設訊息",
|
||||||
|
"edit.message.add.title": "添加",
|
||||||
|
"edit.message.group.title": "訊息組",
|
||||||
|
"edit.message.assistant.title": "助手",
|
||||||
|
"edit.message.assistant.placeholder": "輸入助手消息",
|
||||||
|
"edit.message.user.title": "用戶",
|
||||||
|
"edit.message.user.placeholder": "輸入用戶消息",
|
||||||
|
"edit.message.empty.content": "會話輸入內容不能為空",
|
||||||
|
"edit.model.select.title": "選擇模型",
|
||||||
|
"edit.settings.hide_preset_messages": "隱藏預設消息"
|
||||||
|
},
|
||||||
|
"minapp": {
|
||||||
|
"title": "小程序"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"title": "搜尋話題",
|
||||||
|
"search.placeholder": "搜尋話題或訊息...",
|
||||||
|
"continue_chat": "繼續聊天",
|
||||||
|
"search.topics.empty": "沒有找到相關話題, 點擊回車鍵搜尋所有訊息",
|
||||||
|
"locate.message": "定位到訊息"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
|
"nvidia": "輝達",
|
||||||
|
"zhinao": "360智腦",
|
||||||
|
"hunyuan": "騰訊混元",
|
||||||
|
"fireworks": "Fireworks",
|
||||||
|
"together": "Together",
|
||||||
"openai": "OpenAI",
|
"openai": "OpenAI",
|
||||||
"gemini": "Gemini",
|
"gemini": "Gemini",
|
||||||
"deepseek": "深度求索",
|
"deepseek": "深度求索",
|
||||||
"moonshot": "月之暗面",
|
"moonshot": "月之暗面",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"yi": "零一万物",
|
"yi": "零一萬物",
|
||||||
"zhipu": "智谱AI",
|
"zhipu": "智譜AI",
|
||||||
"groq": "Groq",
|
"groq": "Groq",
|
||||||
"ollama": "Ollama",
|
"ollama": "Ollama",
|
||||||
"baichuan": "百川",
|
"baichuan": "百川",
|
||||||
"dashscope": "DashScope",
|
"dashscope": "阿里雲百鍊",
|
||||||
"anthropic": "Anthropic",
|
"anthropic": "Anthropic",
|
||||||
"aihubmix": "AiHubMix",
|
"aihubmix": "AiHubMix",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
@@ -161,11 +206,13 @@
|
|||||||
"minimax": "MiniMax",
|
"minimax": "MiniMax",
|
||||||
"graphrag-kylin-mountain": "GraphRAG",
|
"graphrag-kylin-mountain": "GraphRAG",
|
||||||
"github": "GitHub Models",
|
"github": "GitHub Models",
|
||||||
"ocoolai": "ocoolAI"
|
"ocoolai": "ocoolAI",
|
||||||
|
"azure-openai": "Azure OpenAI"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "設定",
|
"title": "設定",
|
||||||
"general": "一般設定",
|
"general": "一般設定",
|
||||||
|
"data": "數據設定",
|
||||||
"provider": "模型提供者",
|
"provider": "模型提供者",
|
||||||
"model": "預設模型",
|
"model": "預設模型",
|
||||||
"assistant": "預設助手",
|
"assistant": "預設助手",
|
||||||
@@ -178,7 +225,8 @@
|
|||||||
"messages.input.show_estimated_tokens": "顯示預估輸入 Token 數",
|
"messages.input.show_estimated_tokens": "顯示預估輸入 Token 數",
|
||||||
"messages.input.send_shortcuts": "發送快捷鍵",
|
"messages.input.send_shortcuts": "發送快捷鍵",
|
||||||
"messages.input.paste_long_text_as_file": "將長文本貼上為檔案",
|
"messages.input.paste_long_text_as_file": "將長文本貼上為檔案",
|
||||||
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息",
|
"messages.math_engine": "Markdown 渲染輸入訊息",
|
||||||
|
"messages.math_render_engine": "數學公式引擎",
|
||||||
"general.title": "一般設定",
|
"general.title": "一般設定",
|
||||||
"general.user_name": "使用者名稱",
|
"general.user_name": "使用者名稱",
|
||||||
"general.user_name.placeholder": "輸入您的名稱",
|
"general.user_name.placeholder": "輸入您的名稱",
|
||||||
@@ -186,30 +234,31 @@
|
|||||||
"general.backup.button": "備份",
|
"general.backup.button": "備份",
|
||||||
"general.restore.button": "復原",
|
"general.restore.button": "復原",
|
||||||
"general.view_webdav_settings": "查看 WebDAV 設定",
|
"general.view_webdav_settings": "查看 WebDAV 設定",
|
||||||
"general.webdav.title": "WebDAV",
|
|
||||||
"general.webdav.host": "WebDAV 主機位址",
|
|
||||||
"general.webdav.host.placeholder": "http://localhost:8080",
|
|
||||||
"general.webdav.user": "WebDAV 使用者名稱",
|
|
||||||
"general.webdav.password": "WebDAV 密碼",
|
|
||||||
"general.webdav.path": "WebDAV Path",
|
|
||||||
"general.webdav.path.placeholder": "/backup",
|
|
||||||
"general.webdav.backup.button": "從 WebDAV 備份",
|
|
||||||
"general.webdav.restore.button": "從 WebDAV 恢復",
|
|
||||||
"general.reset.title": "資料重置",
|
"general.reset.title": "資料重置",
|
||||||
"general.reset.button": "重置",
|
"general.reset.button": "重置",
|
||||||
"general.check_update_setting": "更新設定",
|
"general.manually_check_update.title": "關閉更新檢查",
|
||||||
"general.manual_update_check": "手動檢查更新",
|
"data.webdav.title": "WebDAV",
|
||||||
"general.auto_update_check": "自動檢查更新",
|
"data.webdav.host": "WebDAV 主機位址",
|
||||||
|
"data.webdav.host.placeholder": "http://localhost:8080",
|
||||||
|
"data.webdav.user": "WebDAV 使用者名稱",
|
||||||
|
"data.webdav.password": "WebDAV 密碼",
|
||||||
|
"data.webdav.path": "WebDAV Path",
|
||||||
|
"data.webdav.path.placeholder": "/backup",
|
||||||
|
"data.webdav.backup.button": "從 WebDAV 備份",
|
||||||
|
"data.webdav.restore.button": "從 WebDAV 恢復",
|
||||||
"advanced.title": "進階設定",
|
"advanced.title": "進階設定",
|
||||||
"advanced.click_assistant_switch_to_topics": "點擊助手切換到話題",
|
"advanced.click_assistant_switch_to_topics": "點擊助手切換到話題",
|
||||||
"provider.api_key": "API 密鑰",
|
"provider.api_key": "API 密鑰",
|
||||||
"provider.check": "檢查",
|
"provider.check": "檢查",
|
||||||
"provider.get_api_key": "獲取 API 密鑰",
|
"provider.get_api_key": "獲取 API 密鑰",
|
||||||
"provider.api_host": "API 主機地址",
|
"provider.api_host": "API 主機地址",
|
||||||
|
"provider.api_version": "API 版本",
|
||||||
"provider.docs_check": "檢查",
|
"provider.docs_check": "檢查",
|
||||||
"provider.docs_more_details": "查看更多細節",
|
"provider.docs_more_details": "查看更多細節",
|
||||||
"provider.search_placeholder": "搜尋模型 ID 或名稱",
|
"provider.search_placeholder": "搜尋模型 ID 或名稱",
|
||||||
"provider.api.url.reset": "重置",
|
"provider.api.url.reset": "重置",
|
||||||
|
"provider.api.url.preview": "預覽: {{url}}",
|
||||||
|
"provider.api.url.tip": "/結尾忽略v1版本,#結尾強制使用輸入位址",
|
||||||
"models.default_assistant_model": "預設助手模型",
|
"models.default_assistant_model": "預設助手模型",
|
||||||
"models.topic_naming_model": "話題命名模型",
|
"models.topic_naming_model": "話題命名模型",
|
||||||
"models.translate_model": "翻譯模型",
|
"models.translate_model": "翻譯模型",
|
||||||
@@ -257,7 +306,8 @@
|
|||||||
"font_size.title": "訊息字體大小",
|
"font_size.title": "訊息字體大小",
|
||||||
"topic.position": "話題位置",
|
"topic.position": "話題位置",
|
||||||
"topic.position.left": "左側",
|
"topic.position.left": "左側",
|
||||||
"topic.position.right": "右側"
|
"topic.position.right": "右側",
|
||||||
|
"topic.show.time": "顯示話題時間"
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
"title": "翻譯",
|
"title": "翻譯",
|
||||||
@@ -286,9 +336,6 @@
|
|||||||
"keep_alive_time.placeholder": "分鐘",
|
"keep_alive_time.placeholder": "分鐘",
|
||||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。"
|
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。"
|
||||||
},
|
},
|
||||||
"minapp": {
|
|
||||||
"title": "小程序"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"chat.response": "出現錯誤。如果尚未配置 API 密鑰,請前往設定 > 模型提供者中配置密鑰",
|
"chat.response": "出現錯誤。如果尚未配置 API 密鑰,請前往設定 > 模型提供者中配置密鑰",
|
||||||
"backup.file_format": "備份文件格式錯誤"
|
"backup.file_format": "備份文件格式錯誤"
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import './assets/styles/index.scss'
|
import './assets/styles/index.scss'
|
||||||
import './init'
|
import './init'
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
)
|
|
||||||
|
|||||||
160
src/renderer/src/pages/agents/Agents.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { DeleteOutlined, EditOutlined, MoreOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
|
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||||
|
import DragableList from '@renderer/components/DragableList'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
|
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||||
|
import { Agent } from '@renderer/types'
|
||||||
|
import { Button, Dropdown } from 'antd'
|
||||||
|
import { ItemType } from 'antd/es/menu/interface'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import AddAgentPopup from './components/AddAgentPopup'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick: (agent: Agent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Agents: React.FC<Props> = ({ onClick }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { agents, removeAgent, updateAgents } = useAgents()
|
||||||
|
const [dragging, setDragging] = useState(false)
|
||||||
|
|
||||||
|
const getMenuItems = useCallback(
|
||||||
|
(agent: Agent) =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: t('agents.edit.title'),
|
||||||
|
key: 'edit',
|
||||||
|
icon: <EditOutlined />,
|
||||||
|
onClick: () => AssistantSettingsPopup.show({ assistant: agent })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('agents.add.button'),
|
||||||
|
key: 'create',
|
||||||
|
icon: <PlusOutlined />,
|
||||||
|
onClick: () => createAssistantFromAgent(agent)
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: t('common.delete'),
|
||||||
|
key: 'delete',
|
||||||
|
icon: <DeleteOutlined />,
|
||||||
|
danger: true,
|
||||||
|
onClick: () => {
|
||||||
|
window.modal.confirm({
|
||||||
|
centered: true,
|
||||||
|
content: t('agents.delete.popup.content'),
|
||||||
|
onOk: () => removeAgent(agent.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as ItemType[],
|
||||||
|
[removeAgent, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container style={{ paddingBottom: dragging ? 30 : 0 }}>
|
||||||
|
{agents.length > 0 && (
|
||||||
|
<DragableList
|
||||||
|
list={agents}
|
||||||
|
onUpdate={updateAgents}
|
||||||
|
onDragStart={() => setDragging(true)}
|
||||||
|
onDragEnd={() => setDragging(false)}>
|
||||||
|
{(agent: Agent) => (
|
||||||
|
<Dropdown menu={{ items: getMenuItems(agent) }} trigger={['contextMenu']}>
|
||||||
|
<AgentItem onClick={() => onClick(agent)}>
|
||||||
|
<HStack alignItems="center" justifyContent="space-between" h="36px">
|
||||||
|
<AgentItemName className="text-nowrap">
|
||||||
|
{agent.emoji} {agent.name}
|
||||||
|
</AgentItemName>
|
||||||
|
<ActionButton className="actions" gap="15px" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Dropdown menu={{ items: getMenuItems(agent) }} trigger={['hover']}>
|
||||||
|
<MoreOutlined style={{ cursor: 'pointer' }} />
|
||||||
|
</Dropdown>
|
||||||
|
</ActionButton>
|
||||||
|
</HStack>
|
||||||
|
<AgentItemPrompt>{agent.prompt}</AgentItemPrompt>
|
||||||
|
</AgentItem>
|
||||||
|
</Dropdown>
|
||||||
|
)}
|
||||||
|
</DragableList>
|
||||||
|
)}
|
||||||
|
{!dragging && (
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => AddAgentPopup.show()}
|
||||||
|
style={{ borderRadius: 20, height: 34 }}>
|
||||||
|
{t('agents.add.title')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div style={{ height: 10 }} />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled(Scrollbar)`
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-right: 0.5px solid var(--color-border);
|
||||||
|
min-height: calc(100vh - var(--navbar-height));
|
||||||
|
width: var(--assistants-width);
|
||||||
|
`
|
||||||
|
|
||||||
|
const AgentItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 12px;
|
||||||
|
min-height: 38px;
|
||||||
|
border-radius: 10px;
|
||||||
|
user-select: none;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 0.5px solid var(--color-primary);
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const AgentItemName = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const AgentItemPrompt = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-soft);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: -5px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ActionButton = styled(HStack)`
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: none;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--color-icon);
|
||||||
|
`
|
||||||
|
|
||||||
|
export default Agents
|
||||||
@@ -1,52 +1,57 @@
|
|||||||
import { UnorderedListOutlined } from '@ant-design/icons'
|
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { VStack } from '@renderer/components/Layout'
|
||||||
import Agents from '@renderer/config/agents.json'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import SystemAgents from '@renderer/config/agents.json'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { createAssistantFromAgent } from '@renderer/services/assistant'
|
||||||
import { covertAgentToAssistant } from '@renderer/services/assistant'
|
|
||||||
import { Agent } from '@renderer/types'
|
import { Agent } from '@renderer/types'
|
||||||
|
import { uuid } from '@renderer/utils'
|
||||||
import { Col, Row, Typography } from 'antd'
|
import { Col, Row, Typography } from 'antd'
|
||||||
import { find, groupBy } from 'lodash'
|
import { groupBy, omit } from 'lodash'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Agents from './Agents'
|
||||||
import AgentCard from './components/AgentCard'
|
import AgentCard from './components/AgentCard'
|
||||||
import ManageAgentsPopup from './components/ManageAgentsPopup'
|
|
||||||
import UserAgents from './components/UserAgents'
|
|
||||||
|
|
||||||
const { Title } = Typography
|
const { Title } = Typography
|
||||||
|
|
||||||
const AppsPage: FC = () => {
|
const AgentsPage: FC = () => {
|
||||||
const { assistants, addAssistant } = useAssistants()
|
const agentGroups = groupBy(SystemAgents, 'group')
|
||||||
const { agents } = useAgents()
|
|
||||||
const agentGroups = groupBy(Agents, 'group')
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const onAddAgentConfirm = (agent: Agent) => {
|
const getAgentName = (agent: Agent) => {
|
||||||
const added = find(assistants, { id: agent.id })
|
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddAgentConfirm = (agent: Agent) => {
|
||||||
window.modal.confirm({
|
window.modal.confirm({
|
||||||
title: agent.emoji + ' ' + agent.name,
|
title: getAgentName(agent),
|
||||||
content: (agent.description || agent.prompt).substring(0, 1000) + '...',
|
content: (
|
||||||
|
<AgentPrompt>
|
||||||
|
<ReactMarkdown className="markdown">{agent.description || agent.prompt}</ReactMarkdown>
|
||||||
|
</AgentPrompt>
|
||||||
|
),
|
||||||
|
width: 600,
|
||||||
icon: null,
|
icon: null,
|
||||||
closable: true,
|
closable: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
centered: true,
|
centered: true,
|
||||||
okButtonProps: { type: 'primary', disabled: Boolean(added) },
|
okButtonProps: { type: 'primary' },
|
||||||
okText: added ? t('button.added') : t('button.add'),
|
okText: t('agents.add.button'),
|
||||||
onOk: () => onAddAgent(agent)
|
onOk: () => createAssistantFromAgent(agent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAddAgent = (agent: Agent) => {
|
const getAgentFromSystemAgent = (agent: (typeof SystemAgents)[number]) => {
|
||||||
addAssistant(covertAgentToAssistant(agent))
|
return {
|
||||||
window.message.success({
|
...omit(agent, 'group'),
|
||||||
content: t('message.assistant.added.content'),
|
name: agent.name,
|
||||||
key: 'agent-added',
|
id: uuid(),
|
||||||
style: { marginTop: '5vh' }
|
topics: [],
|
||||||
})
|
type: 'agent'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -55,29 +60,32 @@ const AppsPage: FC = () => {
|
|||||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter>
|
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<ContentContainer id="content-container">
|
<ContentContainer id="content-container">
|
||||||
<AssistantsContainer>
|
<Agents onClick={onAddAgentConfirm} />
|
||||||
<HStack alignItems="center" style={{ marginBottom: 16 }}>
|
<AssistantsContainer right>
|
||||||
<Title level={4}>{t('agents.my_agents')}</Title>
|
<VStack style={{ flex: 1 }}>
|
||||||
{agents.length > 0 && <ManageIcon onClick={ManageAgentsPopup.show} />}
|
{Object.keys(agentGroups)
|
||||||
</HStack>
|
.reverse()
|
||||||
<UserAgents onAdd={onAddAgentConfirm} />
|
.map((group) => (
|
||||||
{Object.keys(agentGroups).map((group) => (
|
<div key={group}>
|
||||||
<div key={group}>
|
<Title level={5} key={group} style={{ marginBottom: 16 }}>
|
||||||
<Title level={4} key={group} style={{ marginBottom: 16 }}>
|
{group}
|
||||||
{group}
|
</Title>
|
||||||
</Title>
|
<Row gutter={16}>
|
||||||
<Row gutter={16}>
|
{agentGroups[group].map((agent, index) => {
|
||||||
{agentGroups[group].map((agent, index) => {
|
return (
|
||||||
return (
|
<Col span={8} key={group + index}>
|
||||||
<Col span={8} key={group + index}>
|
<AgentCard
|
||||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent))}
|
||||||
</Col>
|
agent={agent as any}
|
||||||
)
|
/>
|
||||||
})}
|
</Col>
|
||||||
</Row>
|
)
|
||||||
</div>
|
})}
|
||||||
))}
|
</Row>
|
||||||
<div style={{ minHeight: 20 }} />
|
</div>
|
||||||
|
))}
|
||||||
|
<div style={{ minHeight: 20 }} />
|
||||||
|
</VStack>
|
||||||
</AssistantsContainer>
|
</AssistantsContainer>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -97,24 +105,21 @@ const ContentContainer = styled.div`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: scroll;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const AssistantsContainer = styled.div`
|
const AssistantsContainer = styled(Scrollbar)`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
height: calc(100vh - var(--navbar-height));
|
height: calc(100vh - var(--navbar-height));
|
||||||
padding: 20px;
|
padding: 15px 20px;
|
||||||
max-width: 1000px;
|
margin-right: 4px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ManageIcon = styled(UnorderedListOutlined)`
|
const AgentPrompt = styled.div`
|
||||||
font-size: 18px;
|
max-height: 60vh;
|
||||||
color: var(--color-icon);
|
overflow-y: scroll;
|
||||||
cursor: pointer;
|
max-width: 560px;
|
||||||
margin-bottom: 0.5em;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export default AppsPage
|
export default AgentsPage
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import 'emoji-picker-element'
|
|||||||
import { LoadingOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
import { LoadingOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
||||||
import EmojiPicker from '@renderer/components/EmojiPicker'
|
import EmojiPicker from '@renderer/components/EmojiPicker'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { AGENT_PROMPT } from '@renderer/config/prompts'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
import { fetchGenerate } from '@renderer/services/api'
|
import { fetchGenerate } from '@renderer/services/api'
|
||||||
import { syncAgentToAssistant } from '@renderer/services/assistant'
|
import { getDefaultModel } from '@renderer/services/assistant'
|
||||||
import { Agent } from '@renderer/types'
|
import { Agent } from '@renderer/types'
|
||||||
import { getLeadingEmoji, uuid } from '@renderer/utils'
|
import { getLeadingEmoji, uuid } from '@renderer/utils'
|
||||||
import { Button, Form, FormInstance, Input, Modal, Popover } from 'antd'
|
import { Button, Form, FormInstance, Input, Modal, Popover } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
agent?: Agent
|
|
||||||
resolve: (data: Agent | null) => void
|
resolve: (data: Agent | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,13 +24,13 @@ type FieldType = {
|
|||||||
prompt: string
|
prompt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { addAgent, updateAgent } = useAgents()
|
const { addAgent } = useAgents()
|
||||||
const formRef = useRef<FormInstance>(null)
|
const formRef = useRef<FormInstance>(null)
|
||||||
const [emoji, setEmoji] = useState(agent?.emoji)
|
const [emoji, setEmoji] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
const onFinish = (values: FieldType) => {
|
const onFinish = (values: FieldType) => {
|
||||||
@@ -40,26 +40,15 @@ const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agent) {
|
const _agent: Agent = {
|
||||||
const _agent = {
|
|
||||||
...agent,
|
|
||||||
name: values.name,
|
|
||||||
emoji: _emoji,
|
|
||||||
prompt: values.prompt
|
|
||||||
}
|
|
||||||
updateAgent(_agent)
|
|
||||||
syncAgentToAssistant(_agent)
|
|
||||||
resolve(_agent)
|
|
||||||
setOpen(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const _agent = {
|
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: values.name,
|
name: values.name,
|
||||||
emoji: _emoji,
|
emoji: _emoji,
|
||||||
prompt: values.prompt,
|
prompt: values.prompt,
|
||||||
group: 'user'
|
defaultModel: getDefaultModel(),
|
||||||
|
type: 'agent',
|
||||||
|
topics: [],
|
||||||
|
messages: []
|
||||||
}
|
}
|
||||||
|
|
||||||
addAgent(_agent)
|
addAgent(_agent)
|
||||||
@@ -75,18 +64,7 @@ const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
|||||||
resolve(null)
|
resolve(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (agent) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
name: agent.name,
|
|
||||||
prompt: agent.prompt
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [agent, form])
|
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
const prompt = `你是一个专业的 prompt 优化助手,我会给你一段prompt,你需要帮我优化它,仅回复优化后的 prompt 不要添加任何解释,使用 [CRISPE提示框架] 回复。`
|
|
||||||
|
|
||||||
const name = formRef.current?.getFieldValue('name')
|
const name = formRef.current?.getFieldValue('name')
|
||||||
const content = formRef.current?.getFieldValue('prompt')
|
const content = formRef.current?.getFieldValue('prompt')
|
||||||
const promptText = content || name
|
const promptText = content || name
|
||||||
@@ -102,8 +80,10 @@ const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prefixedContent = `请帮我优化下面这段 prompt,使用 CRISPE 提示框架,请使用 Markdown 格式回复,不要使用 codeblock: ${promptText}`
|
const generatedText = await fetchGenerate({
|
||||||
const generatedText = await fetchGenerate({ prompt, content: prefixedContent })
|
prompt: AGENT_PROMPT,
|
||||||
|
content: promptText
|
||||||
|
})
|
||||||
formRef.current?.setFieldValue('prompt', generatedText)
|
formRef.current?.setFieldValue('prompt', generatedText)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching data:', error)
|
console.error('Error fetching data:', error)
|
||||||
@@ -114,13 +94,13 @@ const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={agent ? t('agents.edit.title') : t('agents.add.title')}
|
title={t('agents.add.title')}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={() => formRef.current?.submit()}
|
onOk={() => formRef.current?.submit()}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
afterClose={onClose}
|
afterClose={onClose}
|
||||||
okText={agent ? t('common.save') : t('agents.add.button')}
|
okText={t('agents.add.title')}
|
||||||
centered>
|
centered>
|
||||||
<Form
|
<Form
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
@@ -163,11 +143,10 @@ export default class AddAgentPopup {
|
|||||||
static hide() {
|
static hide() {
|
||||||
TopView.hide('AddAgentPopup')
|
TopView.hide('AddAgentPopup')
|
||||||
}
|
}
|
||||||
static show(agent?: Agent) {
|
static show() {
|
||||||
return new Promise<Agent | null>((resolve) => {
|
return new Promise<Agent | null>((resolve) => {
|
||||||
TopView.show(
|
TopView.show(
|
||||||
<PopupContainer
|
<PopupContainer
|
||||||
agent={agent}
|
|
||||||
resolve={(v) => {
|
resolve={(v) => {
|
||||||
resolve(v)
|
resolve(v)
|
||||||
this.hide()
|
this.hide()
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ const Container = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-mute);
|
border: 0.5px solid var(--color-primary);
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const EmojiHeader = styled.div`
|
const EmojiHeader = styled.div`
|
||||||
@@ -69,6 +70,7 @@ const AgentCardPrompt = styled.div`
|
|||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import { DeleteOutlined, EditOutlined, MenuOutlined } from '@ant-design/icons'
|
|
||||||
import DragableList from '@renderer/components/DragableList'
|
|
||||||
import { Box, HStack } from '@renderer/components/Layout'
|
|
||||||
import { TopView } from '@renderer/components/TopView'
|
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
|
||||||
import { Empty, Modal, Popconfirm } from 'antd'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import AddAgentPopup from './AddAgentPopup'
|
|
||||||
|
|
||||||
const PopupContainer: React.FC = () => {
|
|
||||||
const [open, setOpen] = useState(true)
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { agents, removeAgent, updateAgents } = useAgents()
|
|
||||||
|
|
||||||
const onOk = () => {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = async () => {
|
|
||||||
ManageAgentsPopup.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (agents.length === 0) {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}, [agents])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t('agents.manage.title')}
|
|
||||||
open={open}
|
|
||||||
onOk={onOk}
|
|
||||||
onCancel={onCancel}
|
|
||||||
afterClose={onClose}
|
|
||||||
footer={null}
|
|
||||||
centered>
|
|
||||||
<Container>
|
|
||||||
{agents.length > 0 && (
|
|
||||||
<DragableList list={agents} onUpdate={updateAgents}>
|
|
||||||
{(item) => (
|
|
||||||
<AgentItem>
|
|
||||||
<Box mr={8}>
|
|
||||||
{item.emoji} {item.name}
|
|
||||||
</Box>
|
|
||||||
<HStack gap="15px">
|
|
||||||
<Popconfirm
|
|
||||||
title={t('agents.delete.popup.content')}
|
|
||||||
okButtonProps={{ danger: true }}
|
|
||||||
onConfirm={() => removeAgent(item)}>
|
|
||||||
<DeleteOutlined style={{ color: 'var(--color-error)' }} />
|
|
||||||
</Popconfirm>
|
|
||||||
<EditOutlined style={{ cursor: 'pointer' }} onClick={() => AddAgentPopup.show(item)} />
|
|
||||||
<MenuOutlined style={{ cursor: 'move' }} />
|
|
||||||
</HStack>
|
|
||||||
</AgentItem>
|
|
||||||
)}
|
|
||||||
</DragableList>
|
|
||||||
)}
|
|
||||||
{agents.length === 0 && <Empty description="" />}
|
|
||||||
</Container>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
padding: 12px 0;
|
|
||||||
height: 50vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const AgentItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
user-select: none;
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
.anticon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--color-icon);
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-background-mute);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default class ManageAgentsPopup {
|
|
||||||
static topviewId = 0
|
|
||||||
static hide() {
|
|
||||||
TopView.hide('ManageAgentsPopup')
|
|
||||||
}
|
|
||||||
static show() {
|
|
||||||
TopView.show(<PopupContainer />, 'ManageAgentsPopup')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons'
|
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
|
||||||
import { Agent } from '@renderer/types'
|
|
||||||
import { Col, Row } from 'antd'
|
|
||||||
import { FC } from 'react'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import AddAssistantPopup from './AddAgentPopup'
|
|
||||||
import AgentCard from './AgentCard'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onAdd: (agent: Agent) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserAgents: FC<Props> = ({ onAdd }) => {
|
|
||||||
const { agents } = useAgents()
|
|
||||||
|
|
||||||
const onAddMyAgentClick = () => {
|
|
||||||
AddAssistantPopup.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
|
||||||
{agents.map((agent) => (
|
|
||||||
<Col span={8} key={agent.id}>
|
|
||||||
<AgentCard agent={agent} onClick={() => onAdd(agent)} />
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
<Col span={8}>
|
|
||||||
<AssistantCardContainer style={{ borderStyle: 'dashed' }} onClick={onAddMyAgentClick}>
|
|
||||||
<PlusOutlined />
|
|
||||||
</AssistantCardContainer>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const AssistantCardContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px dashed var(--color-border-soft);
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
min-height: 72px;
|
|
||||||
.anticon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--color-icon);
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default UserAgents
|
|
||||||