Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edbc8560cc | ||
|
|
56761d6f69 | ||
|
|
2b4cfe7cb1 | ||
|
|
6a5faa6610 | ||
|
|
84979a975c | ||
|
|
74740d7fcc | ||
|
|
dff04187be | ||
|
|
a0a13a4015 | ||
|
|
2ad6a1f24c | ||
|
|
cf7c0fc1fc | ||
|
|
4ecbf3edab | ||
|
|
83cc4ccec7 | ||
|
|
3998ad08de | ||
|
|
49a5bc7900 | ||
|
|
7633d70435 | ||
|
|
ad9fb9aa6d | ||
|
|
fc3d15fae8 | ||
|
|
c45fc2bbad | ||
|
|
270216f461 | ||
|
|
112e90c15c | ||
|
|
c579eff86e | ||
|
|
f9f5befc59 | ||
|
|
7271a86677 | ||
|
|
42ede42f62 | ||
|
|
ea7a42f736 | ||
|
|
d2836826e7 | ||
|
|
7d61af7170 | ||
|
|
3f4fa9b0ec | ||
|
|
1bdf6c7955 | ||
|
|
5d005cf5a7 | ||
|
|
1fbd727a7b | ||
|
|
c9813bb1e2 | ||
|
|
edac2004a0 | ||
|
|
a051f9fa44 | ||
|
|
a70e69caf9 | ||
|
|
4896db93fd | ||
|
|
2e7ecbc753 | ||
|
|
f68bd4d8d8 | ||
|
|
d0948e6f8a | ||
|
|
ac9017c031 | ||
|
|
de1d79abb8 | ||
|
|
ad577818dd | ||
|
|
bb50447a98 | ||
|
|
158f9bf1ad | ||
|
|
6a9bc103d7 | ||
|
|
529ec3612e | ||
|
|
d241c38c61 | ||
|
|
ee5ed8c565 | ||
|
|
dc73661678 | ||
|
|
ce973ce3a0 | ||
|
|
a0413158c8 | ||
|
|
6cb3b16451 | ||
|
|
08b0990cf9 | ||
|
|
10b9940edd | ||
|
|
4cbdd563e8 | ||
|
|
dba1f76db7 | ||
|
|
15fb605eb4 | ||
|
|
1bf147fa6a | ||
|
|
a782b2b4aa | ||
|
|
7f92cb59a6 | ||
|
|
6009ae84fb | ||
|
|
038aa2d5cc | ||
|
|
6384525e20 | ||
|
|
3fc7911c97 | ||
|
|
5f55d8c22c | ||
|
|
d9f7bcfc21 | ||
|
|
aa72794967 | ||
|
|
09e6756efe | ||
|
|
dde0400f0d | ||
|
|
1d3a01dd49 | ||
|
|
63cdc15bc2 | ||
|
|
b2818f8619 | ||
|
|
8ef9fb0216 | ||
|
|
63488e6fab | ||
|
|
6d9013f0a1 | ||
|
|
1a68587684 | ||
|
|
47c455b125 | ||
|
|
96124cf58e | ||
|
|
ef975add01 | ||
|
|
ed49066bab | ||
|
|
e7545c5a94 | ||
|
|
fc35df65b8 | ||
|
|
56ca81d245 | ||
|
|
6bc1f4b640 | ||
|
|
ccb216e76a | ||
|
|
60931b85ff | ||
|
|
dc1dbc7bb6 | ||
|
|
5d2efbd62b | ||
|
|
5337017648 | ||
|
|
c409256ae9 | ||
|
|
4ac608052c | ||
|
|
5e6aaabb23 | ||
|
|
8812daeeee | ||
|
|
13e3a8478c | ||
|
|
8687985ccb | ||
|
|
7d54f9b4fa | ||
|
|
6b7ba35183 | ||
|
|
5b42a6d054 | ||
|
|
153e7a9299 | ||
|
|
77e0c5172e | ||
|
|
c50ac440c8 | ||
|
|
34ebab0af8 | ||
|
|
b85765915e | ||
|
|
960f50e4e4 | ||
|
|
65e19d187c | ||
|
|
aa4f94f8a4 | ||
|
|
aa3812eddc | ||
|
|
6b9e58171b | ||
|
|
2f64653b1e | ||
|
|
03dd3038e0 | ||
|
|
f1f7e8e11b | ||
|
|
fbd189c5e1 | ||
|
|
87c3716f75 | ||
|
|
37477587b6 | ||
|
|
d558572d97 | ||
|
|
7506d04c55 | ||
|
|
35fd5aef22 | ||
|
|
8f11d2b1c9 | ||
|
|
9aa2a4727d | ||
|
|
ca6027dd83 | ||
|
|
c2462fd51c | ||
|
|
0739758469 | ||
|
|
b2554333a9 | ||
|
|
6ced973b35 | ||
|
|
ccbeefc546 | ||
|
|
7fdc2db522 | ||
|
|
978f1342e4 | ||
|
|
ff935a656e | ||
|
|
15539a5609 | ||
|
|
88cd4f2144 | ||
|
|
daf2e035b2 | ||
|
|
7ceb4920ec | ||
|
|
0074d5c8b4 | ||
|
|
96737ed695 | ||
|
|
356da1ea67 | ||
|
|
debf996146 | ||
|
|
8d73d1e844 | ||
|
|
b0d777293b | ||
|
|
1a9fbbc0a2 | ||
|
|
ab99a7b96d | ||
|
|
7d561dbfb7 | ||
|
|
6af07c278d | ||
|
|
9c18b851cc | ||
|
|
b1ebe13b5f | ||
|
|
9b258734c4 | ||
|
|
25eb97902b | ||
|
|
2fae6e4a3e | ||
|
|
f312c5fc40 | ||
|
|
afa96549a3 | ||
|
|
6beee78ce8 | ||
|
|
a230ee2c69 | ||
|
|
28a27447a5 | ||
|
|
408976e5dc | ||
|
|
7153996c35 | ||
|
|
73f6a743cd | ||
|
|
3b250d7d78 | ||
|
|
272efaf76e | ||
|
|
44c64a571a | ||
|
|
f817d9136b | ||
|
|
c0f192c6f2 | ||
|
|
b5a109401c | ||
|
|
aeff59946c | ||
|
|
21ad4cfecc | ||
|
|
4df39179bb | ||
|
|
423fdb6992 | ||
|
|
f66adcd217 | ||
|
|
465bf4006c | ||
|
|
14c9cb6001 | ||
|
|
e35d928bcd | ||
|
|
1981f2e648 | ||
|
|
e5c1791135 | ||
|
|
ae1960f5c6 | ||
|
|
51ca9cb289 | ||
|
|
7d2df1a8c5 | ||
|
|
2757535cf0 | ||
|
|
243065221d | ||
|
|
2a674c169e | ||
|
|
100dbc8101 | ||
|
|
67d7ccbf10 | ||
|
|
a71782abb6 | ||
|
|
73973ecb7f | ||
|
|
368de84440 | ||
|
|
a170dbd6f0 | ||
|
|
9b84176a42 | ||
|
|
0f36610e23 | ||
|
|
1e273834b8 | ||
|
|
3b569131a5 | ||
|
|
a4d8e71916 | ||
|
|
39cf227e42 | ||
|
|
d2cad31db4 | ||
|
|
aa864f3876 | ||
|
|
77a8b23d76 |
73
.github/ISSUE_TEMPLATE/#0_bug_report.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: 🐛 错误报告
|
||||||
|
description: 创建一个报告以帮助我们改进
|
||||||
|
title: '[错误]: '
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢您花时间填写此错误报告!
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: 平台
|
||||||
|
description: 您正在使用哪个平台?
|
||||||
|
options:
|
||||||
|
- Windows
|
||||||
|
- macOS
|
||||||
|
- Linux
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: 版本
|
||||||
|
description: 您正在运行的 Cherry Studio 版本是什么?
|
||||||
|
placeholder: 例如 v1.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: 错误描述
|
||||||
|
description: 清晰简洁地描述错误是什么
|
||||||
|
placeholder: 告诉我们发生了什么...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: 重现步骤
|
||||||
|
description: 重现行为的步骤
|
||||||
|
placeholder: |
|
||||||
|
1. 转到 '...'
|
||||||
|
2. 点击 '....'
|
||||||
|
3. 向下滚动到 '....'
|
||||||
|
4. 看到错误
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: 预期行为
|
||||||
|
description: 清晰简洁地描述您期望发生的事情
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: 相关日志输出
|
||||||
|
description: 请复制并粘贴任何相关的日志输出
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: 附加信息
|
||||||
|
description: 在此添加有关问题的任何其他上下文
|
||||||
38
.github/ISSUE_TEMPLATE/#1_feature_request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: 💡 功能建议
|
||||||
|
description: 为项目提出新的想法
|
||||||
|
title: '[功能]: '
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢您花时间提出新的功能建议!
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: 您的功能建议是否与某个问题相关?
|
||||||
|
description: 请简明扼要地描述您遇到的问题
|
||||||
|
placeholder: 我总是感到沮丧,因为...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: 请描述您希望实现的解决方案
|
||||||
|
description: 请简明扼要地描述您希望发生的情况
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: 请描述您考虑过的其他方案
|
||||||
|
description: 请简明扼要地描述您考虑过的任何其他解决方案或功能
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: 其他补充信息
|
||||||
|
description: 在此添加任何其他与功能建议相关的上下文或截图
|
||||||
44
.github/ISSUE_TEMPLATE/#2_question.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: ❓ 提问
|
||||||
|
description: 提出一个问题或寻求帮助
|
||||||
|
title: '[问题]: '
|
||||||
|
labels: ['question']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: question
|
||||||
|
attributes:
|
||||||
|
label: 您的问题
|
||||||
|
description: 请详细描述您的问题
|
||||||
|
placeholder: 请尽可能清楚地说明您的问题...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: 相关背景
|
||||||
|
description: 请提供一些背景信息,帮助我们更好地理解您的问题
|
||||||
|
placeholder: 例如:使用场景、已尝试的解决方案等
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: 补充信息
|
||||||
|
description: 任何其他相关的信息、截图或代码示例
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority
|
||||||
|
attributes:
|
||||||
|
label: 优先级
|
||||||
|
description: 这个问题对您来说有多紧急?
|
||||||
|
options:
|
||||||
|
- 低 (有空再看)
|
||||||
|
- 中 (希望尽快得到答复)
|
||||||
|
- 高 (阻碍工作进行)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
73
.github/ISSUE_TEMPLATE/0_bug_report.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: 🐛 Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: '[Bug]: '
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
description: What platform are you using?
|
||||||
|
options:
|
||||||
|
- Windows
|
||||||
|
- macOS
|
||||||
|
- Linux
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: What version of Cherry Studio are you running?
|
||||||
|
placeholder: e.g. v1.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Bug Description
|
||||||
|
description: A clear and concise description of what the bug is
|
||||||
|
placeholder: Tell us what happened...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A clear and concise description of what you expected to happen
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant Log Output
|
||||||
|
description: Please copy and paste any relevant log output
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context about the problem here
|
||||||
38
.github/ISSUE_TEMPLATE/1_feature_request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: 💡 Feature Request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: '[Feature]: '
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to suggest a new feature!
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Is your feature request related to a problem?
|
||||||
|
description: A clear and concise description of what the problem is
|
||||||
|
placeholder: I'm always frustrated when...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like
|
||||||
|
description: A clear and concise description of what you want to happen
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context or screenshots about the feature request here
|
||||||
44
.github/ISSUE_TEMPLATE/2_question.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: ❓ Question
|
||||||
|
description: Ask a question or seek help
|
||||||
|
title: '[Question]: '
|
||||||
|
labels: ['question']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for asking a question! Please provide as much detail as possible so we can better assist you.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: question
|
||||||
|
attributes:
|
||||||
|
label: Your Question
|
||||||
|
description: Please describe your question in detail
|
||||||
|
placeholder: Please explain your question as clearly as possible...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Context
|
||||||
|
description: Please provide some background information to help us better understand your question
|
||||||
|
placeholder: "For example: use case, solutions you've tried, etc."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Any other relevant information, screenshots, or code examples
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority
|
||||||
|
attributes:
|
||||||
|
label: Priority
|
||||||
|
description: How urgent is this question for you?
|
||||||
|
options:
|
||||||
|
- Low (Can wait)
|
||||||
|
- Medium (Would like a response soon)
|
||||||
|
- High (Blocking progress)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
48
.github/workflows/release.yml
vendored
@@ -2,11 +2,6 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version (e.g. v1.2.3)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
@@ -34,18 +29,37 @@ jobs:
|
|||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.3.1 --activate
|
run: corepack enable && corepack prepare yarn@4.3.1 --activate
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache yarn dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
node_modules
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
||||||
- name: Build Linux
|
- name: Build Linux
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: yarn build:linux
|
run: |
|
||||||
|
yarn build:npm linux
|
||||||
|
yarn build:linux
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Build Mac
|
- name: Build Mac
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
run: yarn build:mac
|
run: |
|
||||||
|
yarn build:npm mac
|
||||||
|
yarn build:mac
|
||||||
env:
|
env:
|
||||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
@@ -61,22 +75,12 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Replace spaces in filenames
|
- name: Replace spaces in filenames
|
||||||
run: node scripts/replaceSpaces.js
|
run: node scripts/replace-spaces.js
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: |
|
allowUpdates: true
|
||||||
dist/*.exe
|
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/*.blockmap'
|
||||||
dist/*.zip
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
dist/*.dmg
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.rpm
|
|
||||||
dist/*.tar.gz
|
|
||||||
dist/latest*.yml
|
|
||||||
dist/*.blockmap
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -36,6 +36,7 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
out
|
out
|
||||||
build/icons
|
build/icons
|
||||||
|
stats.html
|
||||||
|
|
||||||
# ENV
|
# ENV
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
diff --git a/lib/check-signature.js b/lib/check-signature.js
|
|
||||||
index 324568af71bcc4372c9f959131ecd24122848c86..677348e0a138ff608b2ac41f592d813b15ee4956 100644
|
|
||||||
--- a/lib/check-signature.js
|
|
||||||
+++ b/lib/check-signature.js
|
|
||||||
@@ -41,16 +41,12 @@ const spawn_1 = require("./spawn");
|
|
||||||
const debug_1 = __importDefault(require("debug"));
|
|
||||||
const d = (0, debug_1.default)('electron-notarize');
|
|
||||||
const codesignDisplay = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
||||||
- const result = yield (0, spawn_1.spawn)('codesign', ['-dv', '-vvvv', '--deep', path.basename(opts.appPath)], {
|
|
||||||
- cwd: path.dirname(opts.appPath),
|
|
||||||
- });
|
|
||||||
+ const result = yield (0, spawn_1.spawn)('codesign', ['-dv', '-vvvv', '--deep', opts.appPath]);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
const codesign = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
||||||
d('attempting to check codesign of app:', opts.appPath);
|
|
||||||
- const result = yield (0, spawn_1.spawn)('codesign', ['-vvv', '--deep', '--strict', path.basename(opts.appPath)], {
|
|
||||||
- cwd: path.dirname(opts.appPath),
|
|
||||||
- });
|
|
||||||
+ const result = yield (0, spawn_1.spawn)('codesign', ['-vvv', '--deep', '--strict', opts.appPath]);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
function checkSignatures(opts) {
|
|
||||||
diff --git a/lib/notarytool.js b/lib/notarytool.js
|
|
||||||
index 1ab090efb2101fc8bee5553445e0349c54474421..a5ddfd922197449fc56078e4a7e9a2ee5d8d207d 100644
|
|
||||||
--- a/lib/notarytool.js
|
|
||||||
+++ b/lib/notarytool.js
|
|
||||||
@@ -92,9 +92,7 @@ function notarizeAndWaitForNotaryTool(opts) {
|
|
||||||
else {
|
|
||||||
filePath = path.resolve(dir, `${path.parse(opts.appPath).name}.zip`);
|
|
||||||
d('zipping application to:', filePath);
|
|
||||||
- const zipResult = yield (0, spawn_1.spawn)('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', path.basename(opts.appPath), filePath], {
|
|
||||||
- cwd: path.dirname(opts.appPath),
|
|
||||||
- });
|
|
||||||
+ const zipResult = yield (0, spawn_1.spawn)('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', opts.appPath, filePath]);
|
|
||||||
if (zipResult.code !== 0) {
|
|
||||||
throw new Error(`Failed to zip application, exited with code: ${zipResult.code}\n\n${zipResult.output}`);
|
|
||||||
}
|
|
||||||
diff --git a/lib/staple.js b/lib/staple.js
|
|
||||||
index 47dbd85b2fc279d999b57f47fb8171e1cc674436..f8829e6ac54fcd630a730d12d75acc1591b953b6 100644
|
|
||||||
--- a/lib/staple.js
|
|
||||||
+++ b/lib/staple.js
|
|
||||||
@@ -43,9 +43,7 @@ const d = (0, debug_1.default)('electron-notarize:staple');
|
|
||||||
function stapleApp(opts) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
d('attempting to staple app:', opts.appPath);
|
|
||||||
- const result = yield (0, spawn_1.spawn)('xcrun', ['stapler', 'staple', '-v', path.basename(opts.appPath)], {
|
|
||||||
- cwd: path.dirname(opts.appPath),
|
|
||||||
- });
|
|
||||||
+ const result = yield (0, spawn_1.spawn)('xcrun', ['stapler', 'staple', '-v', opts.appPath]);
|
|
||||||
if (result.code !== 0) {
|
|
||||||
throw new Error(`Failed to staple your application with code: ${result.code}\n\n${result.output}`);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
diff --git a/src/libsql-db.js b/src/libsql-db.js
|
||||||
|
index 58c42e4910bd0e53bc497ff9b9702b1f7a961266..250bc97c50a9b790e8798441d904d040f2d2af43 100644
|
||||||
|
--- a/src/libsql-db.js
|
||||||
|
+++ b/src/libsql-db.js
|
||||||
|
@@ -41,9 +41,9 @@ export class LibSqlDb {
|
||||||
|
}
|
||||||
|
async similaritySearch(query, k) {
|
||||||
|
const statement = `SELECT id, pageContent, uniqueLoaderId, source, metadata,
|
||||||
|
- vector_distance_cos(vector, vector32('[${query.join(',')}]'))
|
||||||
|
+ vector_distance_cos(vector, vector32('[${query.join(',')}]')) as distance
|
||||||
|
FROM ${this.tableName}
|
||||||
|
- ORDER BY vector_distance_cos(vector, vector32('[${query.join(',')}]')) ASC
|
||||||
|
+ ORDER BY distance ASC
|
||||||
|
LIMIT ${k};`;
|
||||||
|
this.debug(`Executing statement - ${truncateCenterString(statement, 700)}`);
|
||||||
|
const results = await this.client.execute(statement);
|
||||||
|
@@ -52,7 +52,7 @@ export class LibSqlDb {
|
||||||
|
return {
|
||||||
|
metadata,
|
||||||
|
pageContent: result.pageContent.toString(),
|
||||||
|
- score: 1,
|
||||||
|
+ score: 1 - result.distance,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
diff --git a/src/markdown-loader.js b/src/markdown-loader.js
|
||||||
|
index 8a17cb7f5a68d90d2be21682db6e95ce22a3e71c..9ee868ef9d4ff3dc914b3abc3c8006deb1e9c6c6 100644
|
||||||
|
--- a/src/markdown-loader.js
|
||||||
|
+++ b/src/markdown-loader.js
|
||||||
|
@@ -1,5 +1,4 @@
|
||||||
|
import { micromark } from 'micromark';
|
||||||
|
-import { mdxJsx } from 'micromark-extension-mdx-jsx';
|
||||||
|
import { gfmHtml, gfm } from 'micromark-extension-gfm';
|
||||||
|
import createDebugMessages from 'debug';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
@@ -21,7 +20,7 @@ export class MarkdownLoader extends BaseLoader {
|
||||||
|
? (await getSafe(this.filePathOrUrl, { format: 'buffer' })).body
|
||||||
|
: await stream2buffer(fs.createReadStream(this.filePathOrUrl));
|
||||||
|
this.debug('MarkdownLoader stream created');
|
||||||
|
- const result = micromark(buffer, { extensions: [gfm(), mdxJsx()], htmlExtensions: [gfmHtml()] });
|
||||||
|
+ const result = micromark(buffer, { extensions: [gfm()], htmlExtensions: [gfmHtml()] });
|
||||||
|
this.debug('Markdown parsed...');
|
||||||
|
const webLoader = new WebLoader({
|
||||||
|
urlOrContent: result,
|
||||||
17
.yarn/patches/@llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
diff --git a/src/core/rag-embedding.js b/src/core/rag-embedding.js
|
||||||
|
index 50c3c4064af17bc4c7c46554d8f2419b3afceb0e..632c9b2e04d2e0e3bb09ef1cd8f29d2560e6afc1 100644
|
||||||
|
--- a/src/core/rag-embedding.js
|
||||||
|
+++ b/src/core/rag-embedding.js
|
||||||
|
@@ -1,10 +1,8 @@
|
||||||
|
export class RAGEmbedding {
|
||||||
|
static singleton;
|
||||||
|
static async init(embeddingModel) {
|
||||||
|
- if (!this.singleton) {
|
||||||
|
- await embeddingModel.init();
|
||||||
|
- this.singleton = new RAGEmbedding(embeddingModel);
|
||||||
|
- }
|
||||||
|
+ await embeddingModel.init();
|
||||||
|
+ this.singleton = new RAGEmbedding(embeddingModel);
|
||||||
|
}
|
||||||
|
static getInstance() {
|
||||||
|
return RAGEmbedding.singleton;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
diff --git a/src/util/strings.cjs b/src/util/strings.cjs
|
||||||
|
index 9933cc6e3866c476b47342a29ddb206eb90fa4a5..2965c4f2808bf94af9ef3e2ec889e5552e30e6ae 100644
|
||||||
|
--- a/src/util/strings.cjs
|
||||||
|
+++ b/src/util/strings.cjs
|
||||||
|
@@ -38,13 +38,16 @@ function toTitleCase(str) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function isValidURL(url) {
|
||||||
|
- try {
|
||||||
|
- new URL(url);
|
||||||
|
- return true;
|
||||||
|
- }
|
||||||
|
- catch {
|
||||||
|
- return false;
|
||||||
|
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('ftp://')) {
|
||||||
|
+ try {
|
||||||
|
+ new URL(url);
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+ catch {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
+ return false;
|
||||||
|
}
|
||||||
|
function isValidJson(str) {
|
||||||
|
try {
|
||||||
|
diff --git a/src/util/strings.js b/src/util/strings.js
|
||||||
|
index f5c1655512099b880fc5022e95d5e0c4d1d073f2..1a64bd662a22efd2effd9d2846ffcf0b93391963 100644
|
||||||
|
--- a/src/util/strings.js
|
||||||
|
+++ b/src/util/strings.js
|
||||||
|
@@ -29,13 +29,16 @@ export function toTitleCase(str) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function isValidURL(url) {
|
||||||
|
- try {
|
||||||
|
- new URL(url);
|
||||||
|
- return true;
|
||||||
|
- }
|
||||||
|
- catch {
|
||||||
|
- return false;
|
||||||
|
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('ftp://')) {
|
||||||
|
+ try {
|
||||||
|
+ new URL(url);
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+ catch {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
+ return false;
|
||||||
|
}
|
||||||
|
export function isValidJson(str) {
|
||||||
|
try {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/core.js b/core.js
|
diff --git a/core.js b/core.js
|
||||||
index 00b67a48b7b5cf0029413fc84abd0c01630c3d14..5550b58495b468060f775ca86e4d849d82573ea5 100644
|
index 30c91e66bf595a66c09eb3dbcbda7d58154865f5..b511ff24ea1891904c60174c6ed26ecdd4d5ac51 100644
|
||||||
--- a/core.js
|
--- a/core.js
|
||||||
+++ b/core.js
|
+++ b/core.js
|
||||||
@@ -156,7 +156,7 @@ class APIClient {
|
@@ -156,7 +156,7 @@ class APIClient {
|
||||||
@@ -12,7 +12,7 @@ index 00b67a48b7b5cf0029413fc84abd0c01630c3d14..5550b58495b468060f775ca86e4d849d
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
diff --git a/core.mjs b/core.mjs
|
diff --git a/core.mjs b/core.mjs
|
||||||
index 8bc7a0ee10d61560d7113cf3f703355bb19f7ddd..5e4c8586ea6b13fe887a22af2de05eaa4700b5ec 100644
|
index ac267bcfcff44b1f7c9bea5513bba94726a31795..dd5bd9f29609d3f0eea4bd5b225f302893df14ad 100644
|
||||||
--- a/core.mjs
|
--- a/core.mjs
|
||||||
+++ b/core.mjs
|
+++ b/core.mjs
|
||||||
@@ -149,7 +149,7 @@ export class APIClient {
|
@@ -149,7 +149,7 @@ export class APIClient {
|
||||||
29
.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
diff --git a/lib/pdf-parse.js b/lib/pdf-parse.js
|
||||||
|
index 96bfbc705dcb4fb73cb077a75f02c115371b3477..6d02d2bb426063c3a31cb740c3d86841de162a22 100644
|
||||||
|
--- a/lib/pdf-parse.js
|
||||||
|
+++ b/lib/pdf-parse.js
|
||||||
|
@@ -21,12 +21,12 @@ function render_page(pageData) {
|
||||||
|
for (let item of textContent.items) {
|
||||||
|
if (lastY == item.transform[5] || !lastY){
|
||||||
|
text += item.str;
|
||||||
|
- }
|
||||||
|
+ }
|
||||||
|
else{
|
||||||
|
text += '\n' + item.str;
|
||||||
|
- }
|
||||||
|
+ }
|
||||||
|
lastY = item.transform[5];
|
||||||
|
- }
|
||||||
|
+ }
|
||||||
|
//let strings = textContent.items.map(item => item.str);
|
||||||
|
//let text = strings.join("\n");
|
||||||
|
//text = text.replace(/[ ]+/ig," ");
|
||||||
|
@@ -60,7 +60,7 @@ async function PDF(dataBuffer, options) {
|
||||||
|
if (typeof options.version != 'string') options.version = DEFAULT_OPTIONS.version;
|
||||||
|
if (options.version == 'default') options.version = DEFAULT_OPTIONS.version;
|
||||||
|
|
||||||
|
- PDFJS = PDFJS ? PDFJS : require(`./pdf.js/${options.version}/build/pdf.js`);
|
||||||
|
+ PDFJS = PDFJS ? PDFJS : require(`./pdf.js/v1.10.100/build/pdf.js`);
|
||||||
|
|
||||||
|
ret.version = PDFJS.version;
|
||||||
|
|
||||||
BIN
build/icon.icns
BIN
build/icon.ico
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 41 KiB |
BIN
build/icon.png
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 137 KiB |
BIN
build/logo.png
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 84 KiB |
47
build/nsis-installer.nsh
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
;Inspired by:
|
||||||
|
; https://gist.github.com/bogdibota/062919938e1ed388b3db5ea31f52955c
|
||||||
|
; https://stackoverflow.com/questions/34177547/detect-if-visual-c-redistributable-for-visual-studio-2013-is-installed
|
||||||
|
; https://stackoverflow.com/a/54391388
|
||||||
|
; https://github.com/GitCommons/cpp-redist-nsis/blob/main/installer.nsh
|
||||||
|
|
||||||
|
;Find latests downloads here:
|
||||||
|
; https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
|
||||||
|
|
||||||
|
!include LogicLib.nsh
|
||||||
|
|
||||||
|
; https://github.com/electron-userland/electron-builder/issues/1122
|
||||||
|
!ifndef BUILD_UNINSTALLER
|
||||||
|
Function checkVCRedist
|
||||||
|
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" "Installed"
|
||||||
|
FunctionEnd
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!macro customInit
|
||||||
|
Push $0
|
||||||
|
Call checkVCRedist
|
||||||
|
${If} $0 != "1"
|
||||||
|
MessageBox MB_YESNO "\
|
||||||
|
NOTE: ${PRODUCT_NAME} requires $\r$\n\
|
||||||
|
'Microsoft Visual C++ Redistributable'$\r$\n\
|
||||||
|
to function properly.$\r$\n$\r$\n\
|
||||||
|
Download and install now?" /SD IDYES IDYES InstallVCRedist IDNO DontInstall
|
||||||
|
InstallVCRedist:
|
||||||
|
inetc::get /CAPTION " " /BANNER "Downloading Microsoft Visual C++ Redistributable..." "https://aka.ms/vs/17/release/vc_redist.x64.exe" "$TEMP\vc_redist.x64.exe"
|
||||||
|
ExecWait "$TEMP\vc_redist.x64.exe /install /norestart"
|
||||||
|
;IfErrors InstallError ContinueInstall ; vc_redist exit code is unreliable :(
|
||||||
|
Call checkVCRedist
|
||||||
|
${If} $0 == "1"
|
||||||
|
Goto ContinueInstall
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
;InstallError:
|
||||||
|
MessageBox MB_ICONSTOP "\
|
||||||
|
There was an unexpected error installing$\r$\n\
|
||||||
|
Microsoft Visual C++ Redistributable.$\r$\n\
|
||||||
|
The installation of ${PRODUCT_NAME} cannot continue."
|
||||||
|
DontInstall:
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
ContinueInstall:
|
||||||
|
Pop $0
|
||||||
|
!macroend
|
||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -1,6 +1,8 @@
|
|||||||
# provider: generic
|
# provider: generic
|
||||||
# url: http://127.0.0.1:8080
|
# url: http://127.0.0.1:8080
|
||||||
# updaterCacheDirName: cherry-studio-updater
|
# updaterCacheDirName: cherry-studio-updater
|
||||||
provider: github
|
# provider: github
|
||||||
repo: cherry-studio
|
# repo: cherry-studio
|
||||||
owner: kangfenmao
|
# owner: kangfenmao
|
||||||
|
provider: generic
|
||||||
|
url: https://cherrystudio.ocool.online
|
||||||
|
|||||||
@@ -11,10 +11,31 @@ files:
|
|||||||
- '!src'
|
- '!src'
|
||||||
- '!scripts'
|
- '!scripts'
|
||||||
- '!local'
|
- '!local'
|
||||||
|
- '!docs'
|
||||||
|
- '!packages'
|
||||||
|
- '!stats.html'
|
||||||
|
- '!*.md'
|
||||||
|
- '!**/*.{map,ts,tsx,jsx,less,scss,sass,css.d.ts,d.cts,d.mts,md,markdown,yaml,yml}'
|
||||||
|
- '!**/{test,tests,__tests__,coverage}/**'
|
||||||
|
- '!**/*.{spec,test}.{js,jsx,ts,tsx}'
|
||||||
|
- '!**/*.min.*.map'
|
||||||
|
- '!**/*.d.ts'
|
||||||
|
- '!**/{.DS_Store,Thumbs.db}'
|
||||||
|
- '!**/{LICENSE,LICENSE.txt,LICENSE-MIT.txt,*.LICENSE.txt,NOTICE.txt,README.md,CHANGELOG.md}'
|
||||||
|
- '!node_modules/rollup-plugin-visualizer'
|
||||||
|
- '!node_modules/js-tiktoken'
|
||||||
|
- '!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}'
|
||||||
|
- '!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}'
|
||||||
|
- '!node_modules/html2canvas/dist/{html2canvas.min.js,html2canvas.esm.js}'
|
||||||
asarUnpack:
|
asarUnpack:
|
||||||
- resources/**
|
- resources/**
|
||||||
|
- '**/*.{node,dll,metal,exp,lib}'
|
||||||
win:
|
win:
|
||||||
executableName: Cherry Studio
|
executableName: Cherry Studio
|
||||||
|
artifactName: ${productName}-${version}-portable.${ext}
|
||||||
|
target:
|
||||||
|
- target: nsis
|
||||||
|
- target: portable
|
||||||
nsis:
|
nsis:
|
||||||
artifactName: ${productName}-${version}-setup.${ext}
|
artifactName: ${productName}-${version}-setup.${ext}
|
||||||
shortcutName: ${productName}
|
shortcutName: ${productName}
|
||||||
@@ -22,14 +43,16 @@ nsis:
|
|||||||
createDesktopShortcut: always
|
createDesktopShortcut: always
|
||||||
allowToChangeInstallationDirectory: true
|
allowToChangeInstallationDirectory: true
|
||||||
oneClick: false
|
oneClick: false
|
||||||
|
include: build/nsis-installer.nsh
|
||||||
mac:
|
mac:
|
||||||
entitlementsInherit: build/entitlements.mac.plist
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
|
notarize: false
|
||||||
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
extendInfo:
|
extendInfo:
|
||||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
notarize: false
|
|
||||||
target:
|
target:
|
||||||
- target: dmg
|
- target: dmg
|
||||||
arch:
|
arch:
|
||||||
@@ -39,35 +62,27 @@ mac:
|
|||||||
arch:
|
arch:
|
||||||
- arm64
|
- arm64
|
||||||
- x64
|
- x64
|
||||||
dmg:
|
|
||||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
|
||||||
linux:
|
linux:
|
||||||
|
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||||
target:
|
target:
|
||||||
- target: AppImage
|
- target: AppImage
|
||||||
arch:
|
arch:
|
||||||
- arm64
|
- arm64
|
||||||
- x64
|
- x64
|
||||||
# - snap
|
|
||||||
# - deb
|
|
||||||
maintainer: electronjs.org
|
maintainer: electronjs.org
|
||||||
category: Utility
|
category: Utility
|
||||||
appImage:
|
|
||||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
|
||||||
npmRebuild: false
|
|
||||||
publish:
|
publish:
|
||||||
provider: github
|
provider: generic
|
||||||
repo: cherry-studio
|
url: https://cherrystudio.ocool.online
|
||||||
owner: kangfenmao
|
|
||||||
electronDownload:
|
electronDownload:
|
||||||
mirror: https://npmmirror.com/mirrors/electron/
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
|
afterPack: scripts/after-pack.js
|
||||||
afterSign: scripts/notarize.js
|
afterSign: scripts/notarize.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
输入内容支持快速翻译成英文
|
支持将小程序固定到侧边栏 @hxp0618
|
||||||
输出内容支持翻译成其他语言
|
增加 Grok 和 QwenLM 小程序 @ruiwarn
|
||||||
快速敲击3次空格翻译
|
支持下载模型生成的 CSV 文件
|
||||||
支持自定义快捷键
|
知识库增加刷新按钮
|
||||||
支持关闭对话自动重命名
|
Gemini 搜索增加引用来源
|
||||||
修复 Gemini 自定义域名不生效问题
|
修复模型设置参数无法保存的问题
|
||||||
画图支持生成 Seed 种子词
|
|
||||||
修复 Markdown 渲染错误导致应用崩溃
|
|
||||||
|
|||||||
@@ -1,23 +1,48 @@
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
|
|
||||||
|
const visualizerPlugin = (type: 'renderer' | 'main') => {
|
||||||
|
return process.env[`VISUALIZER_${type.toUpperCase()}`] ? [visualizer({ open: true })] : []
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
main: {
|
main: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [
|
||||||
|
externalizeDepsPlugin({
|
||||||
|
exclude: [
|
||||||
|
'@llm-tools/embedjs',
|
||||||
|
'@llm-tools/embedjs-openai',
|
||||||
|
'@llm-tools/embedjs-loader-web',
|
||||||
|
'@llm-tools/embedjs-loader-markdown',
|
||||||
|
'@llm-tools/embedjs-loader-msoffice',
|
||||||
|
'@llm-tools/embedjs-loader-xml',
|
||||||
|
'@llm-tools/embedjs-loader-pdf',
|
||||||
|
'@llm-tools/embedjs-loader-sitemap',
|
||||||
|
'@llm-tools/embedjs-libsql'
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
...visualizerPlugin('main')
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@main': resolve('src/main'),
|
'@main': resolve('src/main'),
|
||||||
'@types': resolve('src/renderer/src/types'),
|
'@types': resolve('src/renderer/src/types'),
|
||||||
'@shared': resolve('packages/shared')
|
'@shared': resolve('packages/shared')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['@libsql/client']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()]
|
plugins: [externalizeDepsPlugin()]
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
plugins: [react()],
|
plugins: [react(), ...visualizerPlugin('renderer')],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@renderer': resolve('src/renderer/src'),
|
'@renderer': resolve('src/renderer/src'),
|
||||||
@@ -25,7 +50,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: []
|
exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js', 'chunk-AFE5XGNG.js']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
61
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "0.8.20",
|
"version": "0.9.7",
|
||||||
"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",
|
||||||
@@ -11,9 +11,11 @@
|
|||||||
"local",
|
"local",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"nohoist": [
|
"installConfig": {
|
||||||
"packages/database"
|
"hoistingLimits": [
|
||||||
]
|
"packages/database"
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
@@ -23,22 +25,45 @@
|
|||||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
|
"build:check": "yarn typecheck",
|
||||||
"build": "npm run typecheck && electron-vite build",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"build:unpack": "dotenv npm run build && electron-builder --dir",
|
"build:unpack": "dotenv npm run build && electron-builder --dir",
|
||||||
"build:win": "dotenv npm run build && electron-builder --win --publish never",
|
"build:win": "dotenv npm run build && electron-builder --win",
|
||||||
"build:mac": "dotenv electron-vite build && electron-builder --mac --publish never",
|
"build:win:x64": "dotenv npm run build && electron-builder --win --x64",
|
||||||
"build:linux": "dotenv electron-vite build && electron-builder --linux --publish never",
|
"build:mac": "dotenv electron-vite build && electron-builder --mac",
|
||||||
|
"build:mac:arm64": "dotenv electron-vite build && electron-builder --mac --arm64",
|
||||||
|
"build:mac:x64": "dotenv electron-vite build && electron-builder --mac --x64",
|
||||||
|
"build:linux": "dotenv electron-vite build && electron-builder --linux",
|
||||||
|
"build:linux:arm64": "dotenv electron-vite build && electron-builder --linux --arm64",
|
||||||
|
"build:linux:x64": "dotenv electron-vite build && electron-builder --linux --x64",
|
||||||
|
"build:npm": "node scripts/build-npm.js",
|
||||||
"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 -",
|
"pulish:artifacts": "cd packages/artifacts && npm publish && cd -",
|
||||||
"generate:agents": "yarn workspace @cherry-studio/database agents",
|
"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",
|
||||||
|
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||||
|
"analyze:main": "VISUALIZER_MAIN=true yarn build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.0",
|
"@electron-toolkit/preload": "^3.0.0",
|
||||||
"@electron-toolkit/utils": "^3.0.0",
|
"@electron-toolkit/utils": "^3.0.0",
|
||||||
|
"@electron/notarize": "^2.5.0",
|
||||||
|
"@google/generative-ai": "^0.21.0",
|
||||||
|
"@llm-tools/embedjs": "patch:@llm-tools/embedjs@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.25-ec5645cf36.patch",
|
||||||
|
"@llm-tools/embedjs-libsql": "patch:@llm-tools/embedjs-libsql@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-libsql-npm-0.1.25-fad000d74c.patch",
|
||||||
|
"@llm-tools/embedjs-loader-csv": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-loader-markdown": "patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.25-d1d536d640.patch",
|
||||||
|
"@llm-tools/embedjs-loader-msoffice": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-loader-pdf": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-loader-sitemap": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-loader-web": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-loader-xml": "^0.1.25",
|
||||||
|
"@llm-tools/embedjs-openai": "^0.1.25",
|
||||||
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
"apache-arrow": "^18.1.0",
|
||||||
"docx": "^9.0.2",
|
"docx": "^9.0.2",
|
||||||
"electron-log": "^5.1.5",
|
"electron-log": "^5.1.5",
|
||||||
"electron-store": "^8.2.0",
|
"electron-store": "^8.2.0",
|
||||||
@@ -48,6 +73,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"officeparser": "^4.1.1",
|
"officeparser": "^4.1.1",
|
||||||
|
"tokenx": "^0.4.1",
|
||||||
"webdav": "4.11.4"
|
"webdav": "4.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -55,7 +81,6 @@
|
|||||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
"@google/generative-ai": "^0.16.0",
|
|
||||||
"@hello-pangea/dnd": "^16.6.0",
|
"@hello-pangea/dnd": "^16.6.0",
|
||||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
@@ -66,20 +91,21 @@
|
|||||||
"@types/node": "^18.19.9",
|
"@types/node": "^18.19.9",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.18",
|
||||||
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"@types/tinycolor2": "^1",
|
"@types/tinycolor2": "^1",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"antd": "^5.18.3",
|
"antd": "^5.22.5",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"dexie": "^4.0.8",
|
"dexie": "^4.0.8",
|
||||||
"dexie-react-hooks": "^1.1.7",
|
"dexie-react-hooks": "^1.1.7",
|
||||||
"dotenv-cli": "^7.4.2",
|
"dotenv-cli": "^7.4.2",
|
||||||
"electron": "^28.3.3",
|
"electron": "31.7.6",
|
||||||
"electron-builder": "^24.9.1",
|
"electron-builder": "^24.13.3",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-icon-builder": "^2.0.1",
|
"electron-icon-builder": "^2.0.1",
|
||||||
"electron-vite": "^2.0.0",
|
"electron-vite": "^2.3.0",
|
||||||
"emittery": "^1.0.3",
|
"emittery": "^1.0.3",
|
||||||
"emoji-picker-element": "^1.22.1",
|
"emoji-picker-element": "^1.22.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -87,17 +113,16 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.0.0",
|
"eslint-plugin-unused-imports": "^4.0.0",
|
||||||
"gpt-tokens": "^1.3.10",
|
|
||||||
"i18next": "^23.11.5",
|
"i18next": "^23.11.5",
|
||||||
"localforage": "^1.10.0",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"openai": "patch:openai@npm%3A4.71.1#~/.yarn/patches/openai-npm-4.71.1-b5940d6401.patch",
|
"openai": "patch:openai@npm%3A4.76.2#~/.yarn/patches/openai-npm-4.76.2-8ff1374617.patch",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "^4.6.1",
|
"react-hotkeys-hook": "^4.6.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-router": "6",
|
"react-router": "6",
|
||||||
@@ -110,6 +135,7 @@
|
|||||||
"rehype-raw": "^7.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",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"sass": "^1.77.2",
|
"sass": "^1.77.2",
|
||||||
"shiki": "^1.22.2",
|
"shiki": "^1.22.2",
|
||||||
"styled-components": "^6.1.11",
|
"styled-components": "^6.1.11",
|
||||||
@@ -123,7 +149,8 @@
|
|||||||
"react-dom": "^17.0.0 || ^18.0.0"
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@electron/notarize@npm:2.2.1": "patch:@electron/notarize@npm%3A2.3.2#~/.yarn/patches/@electron-notarize-npm-2.3.2-535908a4bd.patch"
|
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
||||||
|
"@llm-tools/embedjs-utils@npm:0.1.25": "patch:@llm-tools/embedjs-utils@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-utils-npm-0.1.25-fd8fe8a193.patch"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.0"
|
"packageManager": "yarn@4.5.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,18 +95,21 @@ export const ZOOM_SHORTCUTS = [
|
|||||||
key: 'zoom_in',
|
key: 'zoom_in',
|
||||||
shortcut: ['CommandOrControl', '='],
|
shortcut: ['CommandOrControl', '='],
|
||||||
editable: false,
|
editable: false,
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
system: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'zoom_out',
|
key: 'zoom_out',
|
||||||
shortcut: ['CommandOrControl', '-'],
|
shortcut: ['CommandOrControl', '-'],
|
||||||
editable: false,
|
editable: false,
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
system: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'zoom_reset',
|
key: 'zoom_reset',
|
||||||
shortcut: ['CommandOrControl', '0'],
|
shortcut: ['CommandOrControl', '0'],
|
||||||
editable: false,
|
editable: false,
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
system: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
202
resources/cherry-studio/releases.html
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Github Releases Timeline</title>
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.5.10/dist/typography.min.css"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="app">
|
||||||
|
<div :class="isDark ? 'dark-bg' : 'bg'" class="min-h-screen">
|
||||||
|
<div class="max-w-3xl mx-auto py-12 px-4">
|
||||||
|
<h1 class="text-3xl font-bold mb-8" :class="isDark ? 'text-white' : 'text-gray-900'">Release Timeline</h1>
|
||||||
|
|
||||||
|
<!-- Loading状态 -->
|
||||||
|
<div v-if="loading" class="text-center py-8">
|
||||||
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-4"
|
||||||
|
:class="isDark ? 'border-gray-700 border-t-blue-500' : 'border-gray-300 border-t-blue-500'"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error 状态 -->
|
||||||
|
<div v-else-if="error" class="text-red-500 text-center py-8">{{ error }}</div>
|
||||||
|
|
||||||
|
<!-- Release 列表 -->
|
||||||
|
<div v-else class="space-y-8">
|
||||||
|
<div v-for="release in releases" :key="release.id" class="relative pl-8"
|
||||||
|
:class="isDark ? 'border-l-2 border-gray-700' : 'border-l-2 border-gray-200'">
|
||||||
|
<div class="absolute -left-2 top-0 w-4 h-4 rounded-full bg-green-500"></div>
|
||||||
|
<div class="rounded-lg shadow-sm p-6 transition-shadow"
|
||||||
|
:class="isDark ? 'bg-black hover:shadow-md hover:shadow-black' : 'bg-white hover:shadow-md'">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold" :class="isDark ? 'text-white' : 'text-gray-900'">
|
||||||
|
{{ release.name || release.tag_name }}
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm mt-1" :class="isDark ? 'text-gray-400' : 'text-gray-500'">
|
||||||
|
{{ formatDate(release.published_at) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium"
|
||||||
|
:class="isDark ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'">
|
||||||
|
{{ release.tag_name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="prose" :class="isDark ? 'text-gray-300 dark-prose' : 'text-gray-600'"
|
||||||
|
v-html="renderMarkdown(release.body)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const md = window.markdownit({
|
||||||
|
breaks: true,
|
||||||
|
linkify: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const { createApp } = Vue
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
releases: [],
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
isDark: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchReleases() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
const response = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch releases')
|
||||||
|
}
|
||||||
|
this.releases = await response.json()
|
||||||
|
} catch (err) {
|
||||||
|
this.error = 'Error loading releases: ' + err.message
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatDate(dateString) {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
renderMarkdown(content) {
|
||||||
|
if (!content) return ''
|
||||||
|
return md.render(content)
|
||||||
|
},
|
||||||
|
initTheme() {
|
||||||
|
// 从 URL 参数获取主题设置
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const theme = url.searchParams.get('theme')
|
||||||
|
this.isDark = theme === 'dark'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initTheme()
|
||||||
|
this.fetchReleases()
|
||||||
|
}
|
||||||
|
}).mount('#app')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 基础的 Markdown 样式 */
|
||||||
|
.prose {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h2 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin: 0.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 0.6em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-left: 1.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
margin-left: 1.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose code {
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose code {
|
||||||
|
background-color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose code {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre code {
|
||||||
|
display: block;
|
||||||
|
padding: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a {
|
||||||
|
color: #3b82f6;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose a {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose blockquote {
|
||||||
|
border-left: 4px solid #e5e7eb;
|
||||||
|
padding-left: 1em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose blockquote {
|
||||||
|
border-left-color: #374151;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose {
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-bg {
|
||||||
|
background-color: #151515;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
45
scripts/after-pack.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const { Arch } = require('electron-builder')
|
||||||
|
const { default: removeLocales } = require('./remove-locales')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
exports.default = async function (context) {
|
||||||
|
await removeLocales(context)
|
||||||
|
const platform = context.packager.platform.name
|
||||||
|
const arch = context.arch
|
||||||
|
|
||||||
|
if (platform === 'mac') {
|
||||||
|
const node_modules_path = path.join(
|
||||||
|
context.appOutDir,
|
||||||
|
'Cherry Studio.app',
|
||||||
|
'Contents',
|
||||||
|
'Resources',
|
||||||
|
'app.asar.unpacked',
|
||||||
|
'node_modules'
|
||||||
|
)
|
||||||
|
|
||||||
|
removeDifferentArchNodeFiles(node_modules_path, '@libsql', arch === Arch.arm64 ? ['darwin-arm64'] : ['darwin-x64'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === 'linux') {
|
||||||
|
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
|
||||||
|
const _arch = arch === Arch.arm64 ? ['linux-arm64-gnu', 'linux-arm64-musl'] : ['linux-x64-gnu', 'linux-x64-musl']
|
||||||
|
removeDifferentArchNodeFiles(node_modules_path, '@libsql', _arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === 'windows') {
|
||||||
|
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
|
||||||
|
removeDifferentArchNodeFiles(node_modules_path, '@libsql', ['win32-x64-msvc'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDifferentArchNodeFiles(nodeModulesPath, packageName, arch) {
|
||||||
|
const modulePath = path.join(nodeModulesPath, packageName)
|
||||||
|
const dirs = fs.readdirSync(modulePath)
|
||||||
|
dirs
|
||||||
|
.filter((dir) => !arch.includes(dir))
|
||||||
|
.forEach((dir) => {
|
||||||
|
fs.rmSync(path.join(modulePath, dir), { recursive: true, force: true })
|
||||||
|
console.log(`Removed dir: ${dir}`, arch)
|
||||||
|
})
|
||||||
|
}
|
||||||
40
scripts/build-npm.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const { downloadNpmPackage } = require('./utils')
|
||||||
|
|
||||||
|
async function downloadNpm(platform) {
|
||||||
|
if (!platform || platform === 'mac') {
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/darwin-arm64',
|
||||||
|
'https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
downloadNpmPackage('@libsql/darwin-x64', 'https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!platform || platform === 'linux') {
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/linux-arm64-gnu',
|
||||||
|
'https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/linux-arm64-musl',
|
||||||
|
'https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/linux-x64-gnu',
|
||||||
|
'https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/linux-x64-musl',
|
||||||
|
'https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!platform || platform === 'windows') {
|
||||||
|
downloadNpmPackage(
|
||||||
|
'@libsql/win32-x64-msvc',
|
||||||
|
'https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformArg = process.argv[2]
|
||||||
|
downloadNpm(platformArg)
|
||||||
58
scripts/remove-locales.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
exports.default = async function (context) {
|
||||||
|
const platform = context.packager.platform.name
|
||||||
|
|
||||||
|
// 根据平台确定 locales 目录位置
|
||||||
|
let resourceDirs = []
|
||||||
|
if (platform === 'mac') {
|
||||||
|
// macOS 的语言文件位置
|
||||||
|
resourceDirs = [
|
||||||
|
path.join(context.appOutDir, 'Cherry Studio.app', 'Contents', 'Resources'),
|
||||||
|
path.join(
|
||||||
|
context.appOutDir,
|
||||||
|
'Cherry Studio.app',
|
||||||
|
'Contents',
|
||||||
|
'Frameworks',
|
||||||
|
'Electron Framework.framework',
|
||||||
|
'Resources'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// Windows 和 Linux 的语言文件位置
|
||||||
|
resourceDirs = [path.join(context.appOutDir, 'locales')]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理每个资源目录
|
||||||
|
for (const resourceDir of resourceDirs) {
|
||||||
|
if (!fs.existsSync(resourceDir)) {
|
||||||
|
console.log(`Resource directory not found: ${resourceDir}, skipping...`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取所有文件和目录
|
||||||
|
const items = fs.readdirSync(resourceDir)
|
||||||
|
|
||||||
|
// 遍历并删除不需要的语言文件
|
||||||
|
for (const item of items) {
|
||||||
|
if (platform === 'mac') {
|
||||||
|
// 在 macOS 上检查 .lproj 目录
|
||||||
|
if (item.endsWith('.lproj') && !item.match(/^(en|zh|ru)/)) {
|
||||||
|
const dirPath = path.join(resourceDir, item)
|
||||||
|
fs.rmSync(dirPath, { recursive: true, force: true })
|
||||||
|
console.log(`Removed locale directory: ${item} from ${resourceDir}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其他平台处理 .pak 文件
|
||||||
|
if (!item.match(/^(en|zh|ru)/)) {
|
||||||
|
const filePath = path.join(resourceDir, item)
|
||||||
|
fs.unlinkSync(filePath)
|
||||||
|
console.log(`Removed locale file: ${item} from ${resourceDir}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Locale cleanup completed!')
|
||||||
|
}
|
||||||
58
scripts/replace-spaces.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// replaceSpaces.js
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const directory = 'dist'
|
||||||
|
|
||||||
|
// 处理文件名中的空格
|
||||||
|
function replaceFileNames() {
|
||||||
|
fs.readdir(directory, (err, files) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const oldPath = path.join(directory, file)
|
||||||
|
const newPath = path.join(directory, file.replace(/ /g, '-'))
|
||||||
|
|
||||||
|
fs.stat(oldPath, (err, stats) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
if (stats.isFile() && oldPath !== newPath) {
|
||||||
|
fs.rename(oldPath, newPath, (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
console.log(`Renamed: ${oldPath} -> ${newPath}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceYmlContent() {
|
||||||
|
fs.readdir(directory, (err, files) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
if (path.extname(file).toLowerCase() === '.yml') {
|
||||||
|
const filePath = path.join(directory, file)
|
||||||
|
|
||||||
|
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||||
|
if (err) throw err
|
||||||
|
|
||||||
|
// 替换内容
|
||||||
|
const newContent = data.replace(/Cherry Studio-/g, 'Cherry-Studio-')
|
||||||
|
|
||||||
|
// 写回文件
|
||||||
|
fs.writeFile(filePath, newContent, 'utf8', (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
console.log(`Updated content in: ${filePath}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行两个操作
|
||||||
|
replaceFileNames()
|
||||||
|
replaceYmlContent()
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// replaceSpaces.js
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const directory = 'dist'
|
|
||||||
|
|
||||||
fs.readdir(directory, (err, files) => {
|
|
||||||
if (err) throw err
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
const oldPath = path.join(directory, file)
|
|
||||||
const newPath = path.join(directory, file.replace(/ /g, '-'))
|
|
||||||
|
|
||||||
fs.stat(oldPath, (err, stats) => {
|
|
||||||
if (err) throw err
|
|
||||||
|
|
||||||
if (stats.isFile() && oldPath !== newPath) {
|
|
||||||
fs.rename(oldPath, newPath, (err) => {
|
|
||||||
if (err) throw err
|
|
||||||
console.log(`Renamed: ${oldPath} -> ${newPath}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
39
scripts/utils.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
|
function downloadNpmPackage(packageName, url) {
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-download-'))
|
||||||
|
|
||||||
|
const targetDir = path.join('./node_modules/', packageName)
|
||||||
|
const filename = packageName.replace('/', '-') + '.tgz'
|
||||||
|
|
||||||
|
// Skip if directory already exists
|
||||||
|
if (fs.existsSync(targetDir)) {
|
||||||
|
console.log(`${targetDir} already exists, skipping download...`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Downloading ${packageName}...`, url)
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
execSync(`curl --fail -o ${filename} ${url}`)
|
||||||
|
|
||||||
|
console.log(`Extracting ${filename}...`)
|
||||||
|
execSync(`tar -xvf ${filename}`)
|
||||||
|
execSync(`rm -rf ${filename}`)
|
||||||
|
execSync(`mv package ${targetDir}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing ${packageName}: ${error.message}`)
|
||||||
|
if (fs.existsSync(filename)) {
|
||||||
|
fs.unlinkSync(filename)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
downloadNpmPackage
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import BackupManager from './services/BackupManager'
|
|||||||
import { configManager } from './services/ConfigManager'
|
import { configManager } from './services/ConfigManager'
|
||||||
import { ExportService } from './services/ExportService'
|
import { ExportService } from './services/ExportService'
|
||||||
import FileStorage from './services/FileStorage'
|
import FileStorage from './services/FileStorage'
|
||||||
|
import { GeminiService } from './services/GeminiService'
|
||||||
|
import KnowledgeService from './services/KnowledgeService'
|
||||||
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
import { compress, decompress } from './utils/zip'
|
import { compress, decompress } from './utils/zip'
|
||||||
@@ -100,6 +102,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
|
|
||||||
// file
|
// file
|
||||||
ipcMain.handle('file:open', fileManager.open)
|
ipcMain.handle('file:open', fileManager.open)
|
||||||
|
ipcMain.handle('file:openPath', fileManager.openPath)
|
||||||
ipcMain.handle('file:save', fileManager.save)
|
ipcMain.handle('file:save', fileManager.save)
|
||||||
ipcMain.handle('file:select', fileManager.selectFile)
|
ipcMain.handle('file:select', fileManager.selectFile)
|
||||||
ipcMain.handle('file:upload', fileManager.uploadFile)
|
ipcMain.handle('file:upload', fileManager.uploadFile)
|
||||||
@@ -144,4 +147,32 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
registerShortcuts(mainWindow)
|
registerShortcuts(mainWindow)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// knowledge base
|
||||||
|
ipcMain.handle('knowledge-base:create', KnowledgeService.create)
|
||||||
|
ipcMain.handle('knowledge-base:reset', KnowledgeService.reset)
|
||||||
|
ipcMain.handle('knowledge-base:delete', KnowledgeService.delete)
|
||||||
|
ipcMain.handle('knowledge-base:add', KnowledgeService.add)
|
||||||
|
ipcMain.handle('knowledge-base:remove', KnowledgeService.remove)
|
||||||
|
ipcMain.handle('knowledge-base:search', KnowledgeService.search)
|
||||||
|
|
||||||
|
// window
|
||||||
|
ipcMain.handle('window:set-minimum-size', (_, width: number, height: number) => {
|
||||||
|
mainWindow?.setMinimumSize(width, height)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('window:reset-minimum-size', () => {
|
||||||
|
mainWindow?.setMinimumSize(1080, 600)
|
||||||
|
const [width, height] = mainWindow?.getSize() ?? [1080, 600]
|
||||||
|
if (width < 1080) {
|
||||||
|
mainWindow?.setSize(1080, height)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// gemini
|
||||||
|
ipcMain.handle('gemini:upload-file', GeminiService.uploadFile)
|
||||||
|
ipcMain.handle('gemini:base64-file', GeminiService.base64File)
|
||||||
|
ipcMain.handle('gemini:retrieve-file', GeminiService.retrieveFile)
|
||||||
|
ipcMain.handle('gemini:list-files', GeminiService.listFiles)
|
||||||
|
ipcMain.handle('gemini:delete-file', GeminiService.deleteFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { app, BrowserWindow, dialog } from 'electron'
|
|||||||
import logger from 'electron-log'
|
import logger from 'electron-log'
|
||||||
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
||||||
|
|
||||||
|
import icon from '../../../build/icon.png?asset'
|
||||||
|
|
||||||
export default class AppUpdater {
|
export default class AppUpdater {
|
||||||
autoUpdater: _AppUpdater = autoUpdater
|
autoUpdater: _AppUpdater = autoUpdater
|
||||||
|
|
||||||
constructor(mainWindow: BrowserWindow) {
|
constructor(mainWindow: BrowserWindow) {
|
||||||
logger.transports.file.level = 'debug'
|
logger.transports.file.level = 'info'
|
||||||
|
|
||||||
autoUpdater.logger = logger
|
autoUpdater.logger = logger
|
||||||
autoUpdater.forceDevUpdateConfig = true
|
autoUpdater.forceDevUpdateConfig = !app.isPackaged
|
||||||
autoUpdater.autoDownload = true
|
autoUpdater.autoDownload = true
|
||||||
|
|
||||||
// 检测下载错误
|
// 检测下载错误
|
||||||
@@ -18,7 +21,7 @@ export default class AppUpdater {
|
|||||||
})
|
})
|
||||||
|
|
||||||
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
||||||
autoUpdater.logger?.info('检测到新版本,开始自动下载')
|
logger.info('检测到新版本', releaseInfo)
|
||||||
mainWindow.webContents.send('update-available', releaseInfo)
|
mainWindow.webContents.send('update-available', releaseInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -29,18 +32,22 @@ export default class AppUpdater {
|
|||||||
|
|
||||||
// 更新下载进度
|
// 更新下载进度
|
||||||
autoUpdater.on('download-progress', (progress) => {
|
autoUpdater.on('download-progress', (progress) => {
|
||||||
logger.info('下载进度', progress)
|
|
||||||
mainWindow.webContents.send('download-progress', progress)
|
mainWindow.webContents.send('download-progress', progress)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当需要更新的内容下载完成后
|
// 当需要更新的内容下载完成后
|
||||||
autoUpdater.on('update-downloaded', () => {
|
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||||||
logger.info('下载完成,询问用户是否更新')
|
mainWindow.webContents.send('update-downloaded')
|
||||||
|
|
||||||
|
logger.info('下载完成,询问用户是否更新', releaseInfo)
|
||||||
|
|
||||||
dialog
|
dialog
|
||||||
.showMessageBox({
|
.showMessageBox({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: '安装更新',
|
title: '安装更新',
|
||||||
message: '更新已下载完成,是否立即安装?',
|
icon,
|
||||||
|
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
||||||
|
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
||||||
buttons: ['稍后安装', '立即安装'],
|
buttons: ['稍后安装', '立即安装'],
|
||||||
defaultId: 1,
|
defaultId: 1,
|
||||||
cancelId: 0
|
cancelId: 0
|
||||||
@@ -49,10 +56,29 @@ export default class AppUpdater {
|
|||||||
if (response === 1) {
|
if (response === 1) {
|
||||||
app.isQuitting = true
|
app.isQuitting = true
|
||||||
setImmediate(() => autoUpdater.quitAndInstall())
|
setImmediate(() => autoUpdater.quitAndInstall())
|
||||||
|
} else {
|
||||||
|
mainWindow.webContents.send('update-downloaded-cancelled')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.autoUpdater = autoUpdater
|
this.autoUpdater = autoUpdater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private formatReleaseNotes(releaseNotes: string | ReleaseNoteInfo[] | null | undefined): string {
|
||||||
|
if (!releaseNotes) {
|
||||||
|
return '暂无更新说明'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof releaseNotes === 'string') {
|
||||||
|
return releaseNotes
|
||||||
|
}
|
||||||
|
|
||||||
|
return releaseNotes.map((note) => note.note).join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReleaseNoteInfo {
|
||||||
|
readonly version: string
|
||||||
|
readonly note: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/main/services/CacheService.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
interface CacheItem<T> {
|
||||||
|
data: T
|
||||||
|
timestamp: number
|
||||||
|
duration: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CacheService {
|
||||||
|
private static cache: Map<string, CacheItem<any>> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cache
|
||||||
|
* @param key Cache key
|
||||||
|
* @param data Cache data
|
||||||
|
* @param duration Cache duration (in milliseconds)
|
||||||
|
*/
|
||||||
|
static set<T>(key: string, data: T, duration: number): void {
|
||||||
|
this.cache.set(key, {
|
||||||
|
data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
duration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache
|
||||||
|
* @param key Cache key
|
||||||
|
* @returns Returns data if cache exists and not expired, otherwise returns null
|
||||||
|
*/
|
||||||
|
static get<T>(key: string): T | null {
|
||||||
|
const item = this.cache.get(key)
|
||||||
|
if (!item) return null
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
if (now - item.timestamp > item.duration) {
|
||||||
|
this.remove(key)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove specific cache
|
||||||
|
* @param key Cache key
|
||||||
|
*/
|
||||||
|
static remove(key: string): void {
|
||||||
|
this.cache.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all cache
|
||||||
|
*/
|
||||||
|
static clear(): void {
|
||||||
|
this.cache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cache exists and is valid
|
||||||
|
* @param key Cache key
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
static has(key: string): boolean {
|
||||||
|
const item = this.cache.get(key)
|
||||||
|
if (!item) return false
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
if (now - item.timestamp > item.duration) {
|
||||||
|
this.remove(key)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,7 +77,10 @@ export class ConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setShortcuts(shortcuts: Shortcut[]) {
|
setShortcuts(shortcuts: Shortcut[]) {
|
||||||
this.store.set('shortcuts', shortcuts)
|
this.store.set(
|
||||||
|
'shortcuts',
|
||||||
|
shortcuts.filter((shortcut) => shortcut.system)
|
||||||
|
)
|
||||||
this.notifySubscribers('shortcuts', shortcuts)
|
this.notifySubscribers('shortcuts', shortcuts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
OpenDialogOptions,
|
OpenDialogOptions,
|
||||||
OpenDialogReturnValue,
|
OpenDialogReturnValue,
|
||||||
SaveDialogOptions,
|
SaveDialogOptions,
|
||||||
SaveDialogReturnValue
|
SaveDialogReturnValue,
|
||||||
|
shell
|
||||||
} from 'electron'
|
} from 'electron'
|
||||||
import logger from 'electron-log'
|
import logger from 'electron-log'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
@@ -298,6 +299,10 @@ class FileStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openPath = async (_: Electron.IpcMainInvokeEvent, path: string): Promise<void> => {
|
||||||
|
shell.openPath(path).catch((err) => logger.error('[IPC - Error] Failed to open file:', err))
|
||||||
|
}
|
||||||
|
|
||||||
public save = async (
|
public save = async (
|
||||||
_: Electron.IpcMainInvokeEvent,
|
_: Electron.IpcMainInvokeEvent,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
|
|||||||
63
src/main/services/GeminiService.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { FileMetadataResponse, FileState, GoogleAIFileManager } from '@google/generative-ai/server'
|
||||||
|
import { FileType } from '@types'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
import { CacheService } from './CacheService'
|
||||||
|
|
||||||
|
export class GeminiService {
|
||||||
|
private static readonly FILE_LIST_CACHE_KEY = 'gemini_file_list'
|
||||||
|
private static readonly CACHE_DURATION = 3000
|
||||||
|
|
||||||
|
static async uploadFile(_: Electron.IpcMainInvokeEvent, file: FileType, apiKey: string) {
|
||||||
|
const fileManager = new GoogleAIFileManager(apiKey)
|
||||||
|
const uploadResult = await fileManager.uploadFile(file.path, {
|
||||||
|
mimeType: 'application/pdf',
|
||||||
|
displayName: file.origin_name
|
||||||
|
})
|
||||||
|
return uploadResult
|
||||||
|
}
|
||||||
|
|
||||||
|
static async base64File(_: Electron.IpcMainInvokeEvent, file: FileType) {
|
||||||
|
return {
|
||||||
|
data: Buffer.from(fs.readFileSync(file.path)).toString('base64'),
|
||||||
|
mimeType: 'application/pdf'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async retrieveFile(
|
||||||
|
_: Electron.IpcMainInvokeEvent,
|
||||||
|
file: FileType,
|
||||||
|
apiKey: string
|
||||||
|
): Promise<FileMetadataResponse | undefined> {
|
||||||
|
const fileManager = new GoogleAIFileManager(apiKey)
|
||||||
|
|
||||||
|
const cachedResponse = CacheService.get<any>(GeminiService.FILE_LIST_CACHE_KEY)
|
||||||
|
if (cachedResponse) {
|
||||||
|
return GeminiService.processResponse(cachedResponse, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fileManager.listFiles()
|
||||||
|
CacheService.set(GeminiService.FILE_LIST_CACHE_KEY, response, GeminiService.CACHE_DURATION)
|
||||||
|
|
||||||
|
return GeminiService.processResponse(response, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static processResponse(response: any, file: FileType) {
|
||||||
|
if (response.files) {
|
||||||
|
return response.files
|
||||||
|
.filter((file) => file.state === FileState.ACTIVE)
|
||||||
|
.find((i) => i.displayName === file.origin_name && Number(i.sizeBytes) === file.size)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
static async listFiles(_: Electron.IpcMainInvokeEvent, apiKey: string) {
|
||||||
|
const fileManager = new GoogleAIFileManager(apiKey)
|
||||||
|
return await fileManager.listFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteFile(_: Electron.IpcMainInvokeEvent, apiKey: string, fileId: string) {
|
||||||
|
const fileManager = new GoogleAIFileManager(apiKey)
|
||||||
|
await fileManager.deleteFile(fileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
156
src/main/services/KnowledgeService.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import * as fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import { LocalPathLoader, RAGApplication, RAGApplicationBuilder, TextLoader } from '@llm-tools/embedjs'
|
||||||
|
import type { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces'
|
||||||
|
import { LibSqlDb } from '@llm-tools/embedjs-libsql'
|
||||||
|
import { MarkdownLoader } from '@llm-tools/embedjs-loader-markdown'
|
||||||
|
import { DocxLoader, ExcelLoader, PptLoader } from '@llm-tools/embedjs-loader-msoffice'
|
||||||
|
import { PdfLoader } from '@llm-tools/embedjs-loader-pdf'
|
||||||
|
import { SitemapLoader } from '@llm-tools/embedjs-loader-sitemap'
|
||||||
|
import { WebLoader } from '@llm-tools/embedjs-loader-web'
|
||||||
|
import { AzureOpenAiEmbeddings, OpenAiEmbeddings } from '@llm-tools/embedjs-openai'
|
||||||
|
import { getInstanceName } from '@main/utils'
|
||||||
|
import { FileType, KnowledgeBaseParams, KnowledgeItem } from '@types'
|
||||||
|
import { app } from 'electron'
|
||||||
|
|
||||||
|
class KnowledgeService {
|
||||||
|
private storageDir = path.join(app.getPath('userData'), 'Data', 'KnowledgeBase')
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initStorageDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
private initStorageDir = (): void => {
|
||||||
|
if (!fs.existsSync(this.storageDir)) {
|
||||||
|
fs.mkdirSync(this.storageDir, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRagApplication = async ({
|
||||||
|
id,
|
||||||
|
model,
|
||||||
|
apiKey,
|
||||||
|
apiVersion,
|
||||||
|
baseURL,
|
||||||
|
dimensions
|
||||||
|
}: KnowledgeBaseParams): Promise<RAGApplication> => {
|
||||||
|
return new RAGApplicationBuilder()
|
||||||
|
.setModel('NO_MODEL')
|
||||||
|
.setEmbeddingModel(
|
||||||
|
apiVersion
|
||||||
|
? new AzureOpenAiEmbeddings({
|
||||||
|
azureOpenAIApiKey: apiKey,
|
||||||
|
azureOpenAIApiVersion: apiVersion,
|
||||||
|
azureOpenAIApiDeploymentName: model,
|
||||||
|
azureOpenAIApiInstanceName: getInstanceName(baseURL),
|
||||||
|
dimensions,
|
||||||
|
batchSize: 10
|
||||||
|
})
|
||||||
|
: new OpenAiEmbeddings({
|
||||||
|
model,
|
||||||
|
apiKey,
|
||||||
|
configuration: { baseURL },
|
||||||
|
dimensions,
|
||||||
|
batchSize: 10
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.setVectorDatabase(new LibSqlDb({ path: path.join(this.storageDir, id) }))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
public create = async (_: Electron.IpcMainInvokeEvent, base: KnowledgeBaseParams): Promise<void> => {
|
||||||
|
this.getRagApplication(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset = async (_: Electron.IpcMainInvokeEvent, { base }: { base: KnowledgeBaseParams }): Promise<void> => {
|
||||||
|
const ragApplication = await this.getRagApplication(base)
|
||||||
|
await ragApplication.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<void> => {
|
||||||
|
const dbPath = path.join(this.storageDir, id)
|
||||||
|
if (fs.existsSync(dbPath)) {
|
||||||
|
fs.rmSync(dbPath, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public add = async (
|
||||||
|
_: Electron.IpcMainInvokeEvent,
|
||||||
|
{ base, item, forceReload = false }: { base: KnowledgeBaseParams; item: KnowledgeItem; forceReload: boolean }
|
||||||
|
): Promise<AddLoaderReturn> => {
|
||||||
|
const ragApplication = await this.getRagApplication(base)
|
||||||
|
|
||||||
|
if (item.type === 'directory') {
|
||||||
|
const directory = item.content as string
|
||||||
|
return await ragApplication.addLoader(new LocalPathLoader({ path: directory }), forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'url') {
|
||||||
|
const content = item.content as string
|
||||||
|
if (content.startsWith('http')) {
|
||||||
|
// @ts-ignore loader type
|
||||||
|
return await ragApplication.addLoader(new WebLoader({ urlOrContent: content }), forceReload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'sitemap') {
|
||||||
|
const content = item.content as string
|
||||||
|
// @ts-ignore loader type
|
||||||
|
return await ragApplication.addLoader(new SitemapLoader({ url: content }), forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'note') {
|
||||||
|
const content = item.content as string
|
||||||
|
return await ragApplication.addLoader(new TextLoader({ text: content }), forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'file') {
|
||||||
|
const file = item.content as FileType
|
||||||
|
|
||||||
|
if (file.ext === '.pdf') {
|
||||||
|
return await ragApplication.addLoader(new PdfLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.ext === '.docx') {
|
||||||
|
return await ragApplication.addLoader(new DocxLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.ext === '.pptx') {
|
||||||
|
return await ragApplication.addLoader(new PptLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.ext === '.xlsx') {
|
||||||
|
return await ragApplication.addLoader(new ExcelLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.md'].includes(file.ext)) {
|
||||||
|
return await ragApplication.addLoader(new MarkdownLoader({ filePathOrUrl: file.path }) as any, forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileContent = fs.readFileSync(file.path, 'utf-8')
|
||||||
|
|
||||||
|
return await ragApplication.addLoader(new TextLoader({ text: fileContent }), forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entriesAdded: 0, uniqueId: '', loaderType: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove = async (
|
||||||
|
_: Electron.IpcMainInvokeEvent,
|
||||||
|
{ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }
|
||||||
|
): Promise<void> => {
|
||||||
|
const ragApplication = await this.getRagApplication(base)
|
||||||
|
await ragApplication.deleteLoader(uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public search = async (
|
||||||
|
_: Electron.IpcMainInvokeEvent,
|
||||||
|
{ search, base }: { search: string; base: KnowledgeBaseParams }
|
||||||
|
): Promise<ExtractChunkData[]> => {
|
||||||
|
const ragApplication = await this.getRagApplication(base)
|
||||||
|
return await ragApplication.search(search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new KnowledgeService()
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Shortcut } from '@types'
|
import { Shortcut } from '@types'
|
||||||
import { BrowserWindow, globalShortcut } from 'electron'
|
import { BrowserWindow, globalShortcut } from 'electron'
|
||||||
|
import Logger from 'electron-log'
|
||||||
|
|
||||||
import { configManager } from './ConfigManager'
|
import { configManager } from './ConfigManager'
|
||||||
|
|
||||||
@@ -55,40 +56,44 @@ export function registerShortcuts(window: BrowserWindow) {
|
|||||||
if (!shortcuts) return
|
if (!shortcuts) return
|
||||||
|
|
||||||
shortcuts.forEach((shortcut) => {
|
shortcuts.forEach((shortcut) => {
|
||||||
if (shortcut.shortcut.length === 0) {
|
try {
|
||||||
return
|
if (shortcut.shortcut.length === 0) {
|
||||||
}
|
return
|
||||||
|
|
||||||
const handler = getShortcutHandler(shortcut)
|
|
||||||
|
|
||||||
if (!handler) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const accelerator = formatShortcutKey(shortcut.shortcut)
|
|
||||||
|
|
||||||
if (shortcut.key === 'show_app') {
|
|
||||||
showAppAccelerator = accelerator
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shortcut.key.includes('zoom')) {
|
|
||||||
switch (shortcut.key) {
|
|
||||||
case 'zoom_in':
|
|
||||||
globalShortcut.register('CommandOrControl+=', () => shortcut.enabled && handler(window))
|
|
||||||
globalShortcut.register('CommandOrControl+numadd', () => shortcut.enabled && handler(window))
|
|
||||||
return
|
|
||||||
case 'zoom_out':
|
|
||||||
globalShortcut.register('CommandOrControl+-', () => shortcut.enabled && handler(window))
|
|
||||||
globalShortcut.register('CommandOrControl+numsub', () => shortcut.enabled && handler(window))
|
|
||||||
return
|
|
||||||
case 'zoom_reset':
|
|
||||||
globalShortcut.register('CommandOrControl+0', () => shortcut.enabled && handler(window))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (shortcut.enabled) {
|
const handler = getShortcutHandler(shortcut)
|
||||||
globalShortcut.register(accelerator, () => handler(window))
|
|
||||||
|
if (!handler) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const accelerator = formatShortcutKey(shortcut.shortcut)
|
||||||
|
|
||||||
|
if (shortcut.key === 'show_app') {
|
||||||
|
showAppAccelerator = accelerator
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcut.key.includes('zoom')) {
|
||||||
|
switch (shortcut.key) {
|
||||||
|
case 'zoom_in':
|
||||||
|
globalShortcut.register('CommandOrControl+=', () => shortcut.enabled && handler(window))
|
||||||
|
globalShortcut.register('CommandOrControl+numadd', () => shortcut.enabled && handler(window))
|
||||||
|
return
|
||||||
|
case 'zoom_out':
|
||||||
|
globalShortcut.register('CommandOrControl+-', () => shortcut.enabled && handler(window))
|
||||||
|
globalShortcut.register('CommandOrControl+numsub', () => shortcut.enabled && handler(window))
|
||||||
|
return
|
||||||
|
case 'zoom_reset':
|
||||||
|
globalShortcut.register('CommandOrControl+0', () => shortcut.enabled && handler(window))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcut.enabled) {
|
||||||
|
globalShortcut.register(accelerator, () => handler(window))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[ShortcutService] Failed to register shortcut ${shortcut.key}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -96,11 +101,15 @@ export function registerShortcuts(window: BrowserWindow) {
|
|||||||
const unregister = () => {
|
const unregister = () => {
|
||||||
if (window.isDestroyed()) return
|
if (window.isDestroyed()) return
|
||||||
|
|
||||||
globalShortcut.unregisterAll()
|
try {
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
|
||||||
if (showAppAccelerator) {
|
if (showAppAccelerator) {
|
||||||
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
|
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
|
||||||
handler && globalShortcut.register(showAppAccelerator, () => handler(window))
|
handler && globalShortcut.register(showAppAccelerator, () => handler(window))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('[ShortcutService] Failed to unregister shortcuts')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +122,10 @@ export function registerShortcuts(window: BrowserWindow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function unregisterAllShortcuts() {
|
export function unregisterAllShortcuts() {
|
||||||
showAppAccelerator = null
|
try {
|
||||||
globalShortcut.unregisterAll()
|
showAppAccelerator = null
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('[ShortcutService] Failed to unregister all shortcuts')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import { isLinux, isWin } from '@main/constant'
|
import { isLinux, isWin } from '@main/constant'
|
||||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
|
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
|
||||||
|
import Logger from 'electron-log'
|
||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { join } from 'path'
|
import path, { join } from 'path'
|
||||||
|
|
||||||
import icon from '../../../build/icon.png?asset'
|
import icon from '../../../build/icon.png?asset'
|
||||||
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
||||||
@@ -12,6 +13,8 @@ import { configManager } from './ConfigManager'
|
|||||||
export class WindowService {
|
export class WindowService {
|
||||||
private static instance: WindowService | null = null
|
private static instance: WindowService | null = null
|
||||||
private mainWindow: BrowserWindow | null = null
|
private mainWindow: BrowserWindow | null = null
|
||||||
|
private isQuitting: boolean = false
|
||||||
|
private wasFullScreen: boolean = false
|
||||||
|
|
||||||
public static getInstance(): WindowService {
|
public static getInstance(): WindowService {
|
||||||
if (!WindowService.instance) {
|
if (!WindowService.instance) {
|
||||||
@@ -32,6 +35,7 @@ export class WindowService {
|
|||||||
|
|
||||||
const theme = configManager.getTheme()
|
const theme = configManager.getTheme()
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
|
const isLinux = process.platform === 'linux'
|
||||||
|
|
||||||
this.mainWindow = new BrowserWindow({
|
this.mainWindow = new BrowserWindow({
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
@@ -40,12 +44,12 @@ export class WindowService {
|
|||||||
height: mainWindowState.height,
|
height: mainWindowState.height,
|
||||||
minWidth: 1080,
|
minWidth: 1080,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
show: true,
|
show: false, // 初始不显示
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
transparent: isMac,
|
transparent: isMac,
|
||||||
vibrancy: 'under-window',
|
vibrancy: 'under-window',
|
||||||
visualEffectState: 'active',
|
visualEffectState: 'active',
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: isLinux ? 'default' : 'hidden',
|
||||||
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
||||||
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
|
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
|
||||||
trafficLightPosition: { x: 8, y: 12 },
|
trafficLightPosition: { x: 8, y: 12 },
|
||||||
@@ -116,19 +120,44 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupWindowEvents(mainWindow: BrowserWindow) {
|
private setupWindowEvents(mainWindow: BrowserWindow) {
|
||||||
mainWindow.on('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
mainWindow.show()
|
mainWindow.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 处理全屏相关事件
|
||||||
|
mainWindow.on('enter-full-screen', () => {
|
||||||
|
this.wasFullScreen = true
|
||||||
|
mainWindow.webContents.send('fullscreen-status-changed', true)
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.on('leave-full-screen', () => {
|
||||||
|
this.wasFullScreen = false
|
||||||
|
mainWindow.webContents.send('fullscreen-status-changed', false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupWebContentsHandlers(mainWindow: BrowserWindow) {
|
private setupWebContentsHandlers(mainWindow: BrowserWindow) {
|
||||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||||
|
if (url.includes('localhost:5173')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
shell.openExternal(url)
|
shell.openExternal(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
shell.openExternal(details.url)
|
const { url } = details
|
||||||
|
|
||||||
|
if (url.includes('http://file/')) {
|
||||||
|
const fileName = url.replace('http://file/', '')
|
||||||
|
const storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
||||||
|
const filePath = storageDir + '/' + fileName
|
||||||
|
shell.openPath(filePath).catch((err) => Logger.error('Failed to open file:', err))
|
||||||
|
} else {
|
||||||
|
shell.openExternal(details.url)
|
||||||
|
}
|
||||||
|
|
||||||
return { action: 'deny' }
|
return { action: 'deny' }
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -166,6 +195,11 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupWindowLifecycleEvents(mainWindow: BrowserWindow) {
|
private setupWindowLifecycleEvents(mainWindow: BrowserWindow) {
|
||||||
|
// 监听应用退出事件
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
this.isQuitting = true
|
||||||
|
})
|
||||||
|
|
||||||
mainWindow.on('close', (event) => {
|
mainWindow.on('close', (event) => {
|
||||||
const notInTray = !configManager.isTray()
|
const notInTray = !configManager.isTray()
|
||||||
|
|
||||||
@@ -175,9 +209,15 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mac
|
// Mac
|
||||||
if (!app.isQuitting) {
|
if (!this.isQuitting) {
|
||||||
event.preventDefault()
|
if (this.wasFullScreen) {
|
||||||
mainWindow.hide()
|
// 如果是全屏状态,直接退出
|
||||||
|
this.isQuitting = true
|
||||||
|
app.quit()
|
||||||
|
} else {
|
||||||
|
event.preventDefault()
|
||||||
|
mainWindow.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,3 +14,11 @@ export function getDataPath() {
|
|||||||
}
|
}
|
||||||
return dataPath
|
return dataPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getInstanceName(baseURL: string) {
|
||||||
|
try {
|
||||||
|
return new URL(baseURL).host.split('.')[0]
|
||||||
|
} catch (error) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import EnUs from '../../renderer/src/i18n/locales/en-us.json'
|
import EnUs from '../../renderer/src/i18n/locales/en-us.json'
|
||||||
|
import JaJP from '../../renderer/src/i18n/locales/ja-jp.json'
|
||||||
import RuRu from '../../renderer/src/i18n/locales/ru-ru.json'
|
import RuRu from '../../renderer/src/i18n/locales/ru-ru.json'
|
||||||
import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
|
import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
|
||||||
import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
|
import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
|
||||||
@@ -7,6 +8,7 @@ const locales = {
|
|||||||
'en-US': EnUs,
|
'en-US': EnUs,
|
||||||
'zh-CN': ZhCn,
|
'zh-CN': ZhCn,
|
||||||
'zh-TW': ZhTw,
|
'zh-TW': ZhTw,
|
||||||
|
'ja-JP': JaJP,
|
||||||
'ru-RU': RuRu
|
'ru-RU': RuRu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
src/preload/index.d.ts
vendored
@@ -1,8 +1,11 @@
|
|||||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||||
|
import type { FileMetadataResponse, ListFilesResponse, UploadFileResponse } from '@google/generative-ai/server'
|
||||||
|
import { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces'
|
||||||
import { FileType } from '@renderer/types'
|
import { FileType } from '@renderer/types'
|
||||||
import { WebDavConfig } from '@renderer/types'
|
import { WebDavConfig } from '@renderer/types'
|
||||||
import { AppInfo, LanguageVarious } from '@renderer/types'
|
import { AppInfo, KnowledgeBaseParams, KnowledgeItem, LanguageVarious } from '@renderer/types'
|
||||||
import type { OpenDialogOptions } from 'electron'
|
import type { OpenDialogOptions } from 'electron'
|
||||||
|
import type { UpdateInfo } from 'electron-updater'
|
||||||
import { Readable } from 'stream'
|
import { Readable } from 'stream'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -10,7 +13,7 @@ declare global {
|
|||||||
electron: ElectronAPI
|
electron: ElectronAPI
|
||||||
api: {
|
api: {
|
||||||
getAppInfo: () => Promise<AppInfo>
|
getAppInfo: () => Promise<AppInfo>
|
||||||
checkForUpdate: () => void
|
checkForUpdate: () => Promise<{ currentVersion: string; updateInfo: UpdateInfo | null }>
|
||||||
openWebsite: (url: string) => void
|
openWebsite: (url: string) => void
|
||||||
setProxy: (proxy: string | undefined) => void
|
setProxy: (proxy: string | undefined) => void
|
||||||
setLanguage: (theme: LanguageVarious) => void
|
setLanguage: (theme: LanguageVarious) => void
|
||||||
@@ -40,6 +43,7 @@ declare global {
|
|||||||
create: (fileName: string) => Promise<string>
|
create: (fileName: string) => Promise<string>
|
||||||
write: (filePath: string, data: Uint8Array | string) => Promise<void>
|
write: (filePath: string, data: Uint8Array | string) => Promise<void>
|
||||||
open: (options?: OpenDialogOptions) => Promise<{ fileName: string; filePath: string; content: Buffer } | null>
|
open: (options?: OpenDialogOptions) => Promise<{ fileName: string; filePath: string; content: Buffer } | null>
|
||||||
|
openPath: (path: string) => Promise<void>
|
||||||
save: (
|
save: (
|
||||||
path: string,
|
path: string,
|
||||||
content: string | NodeJS.ArrayBufferView,
|
content: string | NodeJS.ArrayBufferView,
|
||||||
@@ -57,6 +61,33 @@ declare global {
|
|||||||
shortcuts: {
|
shortcuts: {
|
||||||
update: (shortcuts: Shortcut[]) => Promise<void>
|
update: (shortcuts: Shortcut[]) => Promise<void>
|
||||||
}
|
}
|
||||||
|
knowledgeBase: {
|
||||||
|
create: ({ id, model, apiKey, baseURL }: KnowledgeBaseParams) => Promise<void>
|
||||||
|
reset: ({ base }: { base: KnowledgeBaseParams }) => Promise<void>
|
||||||
|
delete: (id: string) => Promise<void>
|
||||||
|
add: ({
|
||||||
|
base,
|
||||||
|
item,
|
||||||
|
forceReload = false
|
||||||
|
}: {
|
||||||
|
base: KnowledgeBaseParams
|
||||||
|
item: KnowledgeItem
|
||||||
|
forceReload?: boolean
|
||||||
|
}) => Promise<AddLoaderReturn>
|
||||||
|
remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise<void>
|
||||||
|
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise<ExtractChunkData[]>
|
||||||
|
}
|
||||||
|
window: {
|
||||||
|
setMinimumSize: (width: number, height: number) => Promise<void>
|
||||||
|
resetMinimumSize: () => Promise<void>
|
||||||
|
}
|
||||||
|
gemini: {
|
||||||
|
uploadFile: (file: FileType, apiKey: string) => Promise<UploadFileResponse>
|
||||||
|
retrieveFile: (file: FileType, apiKey: string) => Promise<FileMetadataResponse | undefined>
|
||||||
|
base64File: (file: FileType) => Promise<{ data: string; mimeType: string }>
|
||||||
|
listFiles: (apiKey: string) => Promise<ListFilesResponse>
|
||||||
|
deleteFile: (apiKey: string, fileId: string) => Promise<void>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
import { Shortcut, WebDavConfig } from '@types'
|
import { FileType, KnowledgeBaseParams, KnowledgeItem, Shortcut, WebDavConfig } from '@types'
|
||||||
import { contextBridge, ipcRenderer, OpenDialogOptions } from 'electron'
|
import { contextBridge, ipcRenderer, OpenDialogOptions } from 'electron'
|
||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
@@ -36,6 +36,7 @@ const api = {
|
|||||||
create: (fileName: string) => ipcRenderer.invoke('file:create', fileName),
|
create: (fileName: string) => ipcRenderer.invoke('file:create', fileName),
|
||||||
write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke('file:write', filePath, data),
|
write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke('file:write', filePath, data),
|
||||||
open: (options?: { decompress: boolean }) => ipcRenderer.invoke('file:open', options),
|
open: (options?: { decompress: boolean }) => ipcRenderer.invoke('file:open', options),
|
||||||
|
openPath: (path: string) => ipcRenderer.invoke('file:openPath', path),
|
||||||
save: (path: string, content: string, options?: { compress: boolean }) =>
|
save: (path: string, content: string, options?: { compress: boolean }) =>
|
||||||
ipcRenderer.invoke('file:save', path, content, options),
|
ipcRenderer.invoke('file:save', path, content, options),
|
||||||
selectFolder: () => ipcRenderer.invoke('file:selectFolder'),
|
selectFolder: () => ipcRenderer.invoke('file:selectFolder'),
|
||||||
@@ -50,6 +51,36 @@ const api = {
|
|||||||
openPath: (path: string) => ipcRenderer.invoke('open:path', path),
|
openPath: (path: string) => ipcRenderer.invoke('open:path', path),
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
update: (shortcuts: Shortcut[]) => ipcRenderer.invoke('shortcuts:update', shortcuts)
|
update: (shortcuts: Shortcut[]) => ipcRenderer.invoke('shortcuts:update', shortcuts)
|
||||||
|
},
|
||||||
|
knowledgeBase: {
|
||||||
|
create: ({ id, model, apiKey, baseURL }: KnowledgeBaseParams) =>
|
||||||
|
ipcRenderer.invoke('knowledge-base:create', { id, model, apiKey, baseURL }),
|
||||||
|
reset: ({ base }: { base: KnowledgeBaseParams }) => ipcRenderer.invoke('knowledge-base:reset', { base }),
|
||||||
|
delete: (id: string) => ipcRenderer.invoke('knowledge-base:delete', id),
|
||||||
|
add: ({
|
||||||
|
base,
|
||||||
|
item,
|
||||||
|
forceReload = false
|
||||||
|
}: {
|
||||||
|
base: KnowledgeBaseParams
|
||||||
|
item: KnowledgeItem
|
||||||
|
forceReload?: boolean
|
||||||
|
}) => ipcRenderer.invoke('knowledge-base:add', { base, item, forceReload }),
|
||||||
|
remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) =>
|
||||||
|
ipcRenderer.invoke('knowledge-base:remove', { uniqueId, base }),
|
||||||
|
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) =>
|
||||||
|
ipcRenderer.invoke('knowledge-base:search', { search, base })
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
setMinimumSize: (width: number, height: number) => ipcRenderer.invoke('window:set-minimum-size', width, height),
|
||||||
|
resetMinimumSize: () => ipcRenderer.invoke('window:reset-minimum-size')
|
||||||
|
},
|
||||||
|
gemini: {
|
||||||
|
uploadFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:upload-file', file, apiKey),
|
||||||
|
base64File: (file: FileType) => ipcRenderer.invoke('gemini:base64-file', file),
|
||||||
|
retrieveFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:retrieve-file', file, apiKey),
|
||||||
|
listFiles: (apiKey: string) => ipcRenderer.invoke('gemini:list-files', apiKey),
|
||||||
|
deleteFile: (apiKey: string, fileId: string) => ipcRenderer.invoke('gemini:delete-file', apiKey, fileId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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 HomePage from './pages/home/HomePage'
|
import HomePage from './pages/home/HomePage'
|
||||||
|
import KnowledgePage from './pages/knowledge/KnowledgePage'
|
||||||
import PaintingsPage from './pages/paintings/PaintingsPage'
|
import PaintingsPage from './pages/paintings/PaintingsPage'
|
||||||
import SettingsPage from './pages/settings/SettingsPage'
|
import SettingsPage from './pages/settings/SettingsPage'
|
||||||
import TranslatePage from './pages/translate/TranslatePage'
|
import TranslatePage from './pages/translate/TranslatePage'
|
||||||
@@ -30,10 +31,11 @@ function App(): JSX.Element {
|
|||||||
<Sidebar />
|
<Sidebar />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/files" element={<FilesPage />} />
|
|
||||||
<Route path="/agents" element={<AgentsPage />} />
|
<Route path="/agents" element={<AgentsPage />} />
|
||||||
<Route path="/paintings" element={<PaintingsPage />} />
|
<Route path="/paintings" element={<PaintingsPage />} />
|
||||||
<Route path="/translate" element={<TranslatePage />} />
|
<Route path="/translate" element={<TranslatePage />} />
|
||||||
|
<Route path="/files" element={<FilesPage />} />
|
||||||
|
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||||
<Route path="/apps" element={<AppsPage />} />
|
<Route path="/apps" element={<AppsPage />} />
|
||||||
<Route path="/settings/*" element={<SettingsPage />} />
|
<Route path="/settings/*" element={<SettingsPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,88 +1,91 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4563475 */
|
font-family: "iconfont"; /* Project id 4753420 */
|
||||||
src: url('iconfont.woff2?t=1725606177995') format('woff2');
|
src: url('iconfont.woff2?t=1736309723926') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1736309723926') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1736309723926') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-family: 'iconfont' !important;
|
font-family: "iconfont" !important;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-at:before {
|
||||||
|
content: "\e623";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon-adaptive-width:before {
|
||||||
|
content: "\e87a";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-a-darkmode:before {
|
.icon-a-darkmode:before {
|
||||||
content: '\e6cd';
|
content: "\e6cd";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-ai-model:before {
|
.icon-ai-model:before {
|
||||||
content: '\e827';
|
content: "\e827";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-ai-model1:before {
|
.icon-ai-model1:before {
|
||||||
content: '\ec09';
|
content: "\ec09";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-gridlines:before {
|
.icon-gridlines:before {
|
||||||
content: '\e942';
|
content: "\e942";
|
||||||
}
|
|
||||||
|
|
||||||
.icon-grid-row-2copy:before {
|
|
||||||
content: '\e681';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-inbox:before {
|
.icon-inbox:before {
|
||||||
content: '\e869';
|
content: "\e869";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-business-smart-assistant:before {
|
.icon-business-smart-assistant:before {
|
||||||
content: '\e601';
|
content: "\e601";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-copy:before {
|
.icon-copy:before {
|
||||||
content: '\e6ae';
|
content: "\e6ae";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-ic_send:before {
|
.icon-ic_send:before {
|
||||||
content: '\e795';
|
content: "\e795";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-dark1:before {
|
.icon-dark1:before {
|
||||||
content: '\e72f';
|
content: "\e72f";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-theme-light:before {
|
.icon-theme-light:before {
|
||||||
content: '\e6b7';
|
content: "\e6b7";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-translate_line:before {
|
.icon-translate_line:before {
|
||||||
content: '\e7de';
|
content: "\e7de";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-history:before {
|
.icon-history:before {
|
||||||
content: '\e758';
|
content: "\e758";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-hide-sidebar:before {
|
.icon-hide-sidebar:before {
|
||||||
content: '\e8eb';
|
content: "\e8eb";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-show-sidebar:before {
|
.icon-show-sidebar:before {
|
||||||
content: '\e944';
|
content: "\e944";
|
||||||
}
|
|
||||||
|
|
||||||
.icon-a-addchat:before {
|
|
||||||
content: '\e658';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-appstore:before {
|
.icon-appstore:before {
|
||||||
content: '\e792';
|
content: "\e792";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-chat:before {
|
.icon-chat:before {
|
||||||
content: '\e615';
|
content: "\e615";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-setting:before {
|
.icon-setting:before {
|
||||||
content: '\e78e';
|
content: "\e78e";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
src/renderer/src/assets/images/apps/genspark.jpg
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/renderer/src/assets/images/apps/github-copilot.webp
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/renderer/src/assets/images/apps/grok.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/renderer/src/assets/images/apps/hika.webp
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/renderer/src/assets/images/apps/nm.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/renderer/src/assets/images/apps/qwenlm.webp
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/renderer/src/assets/images/apps/thinkany.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 84 KiB |
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="_图层_2" data-name="图层_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.45 66.73">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #ea5e5d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #23af69;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-3 {
|
|
||||||
fill: #ea5756;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g id="_图层_1-2" data-name="图层_1">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-1" d="M16.72,51.21c-4.45,0-8.64-1.78-11.81-5.01-3.17-3.23-4.91-7.51-4.91-12.04s1.74-8.81,4.91-12.04,7.36-5.01,11.81-5.01,8.71,1.82,11.82,4.99c2.32,2.36,2.32,6.2,0,8.56-2.32,2.36-6.08,2.36-8.4,0-.9-.92-2.15-1.45-3.43-1.45-2.63,0-4.85,2.26-4.85,4.94s2.22,4.94,4.85,4.94c1.28,0,2.52-.53,3.43-1.45,2.32-2.36,6.08-2.36,8.4,0,2.32,2.36,2.32,6.2,0,8.56-3.11,3.17-7.42,4.99-11.82,4.99Z"/>
|
|
||||||
<path class="cls-1" d="M32.05,66.73c-4.45,0-8.64-1.78-11.81-5.01s-4.91-7.51-4.91-12.04,1.79-8.88,4.9-12.06c2.32-2.36,6.08-2.36,8.4,0,2.32,2.36,2.32,6.2,0,8.56-.9.92-1.42,2.19-1.42,3.49,0,2.68,2.22,4.94,4.85,4.94s4.85-2.26,4.85-4.94c0-.95-.23-2.31-1.32-3.43-3.13-3.19-4.92-7.6-4.92-12.09s1.74-8.81,4.91-12.04,7.36-5.01,11.81-5.01,8.64,1.78,11.81,5.01,4.91,7.51,4.91,12.04-1.79,8.88-4.9,12.06c-2.32,2.36-6.08,2.36-8.4,0-2.32-2.36-2.32-6.2,0-8.56.9-.92,1.42-2.19,1.42-3.49,0-2.68-2.22-4.94-4.85-4.94s-4.85,2.26-4.85,4.94c0,1.31.53,2.6,1.45,3.53,3.1,3.16,4.8,7.42,4.8,11.99s-1.74,8.81-4.91,12.04c-3.17,3.23-7.36,5.01-11.81,5.01Z"/>
|
|
||||||
</g>
|
|
||||||
<path class="cls-2" d="M32.05,19.09l-9.72-9.12c-1.5-1.4-1.57-3.75-.17-5.25,1.4-1.49,3.75-1.57,5.25-.17l3.89,3.65,5.53-6.83c1.29-1.59,3.63-1.84,5.22-.55,1.59,1.29,1.84,3.63.55,5.22l-10.56,13.05Z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M93.93,24.6l.55-.39c.69-.4,1.17-.61,1.46-.61.63,0,1.3.57,2.03,1.7.44.71.67,1.27.67,1.7s-.14.78-.41,1.06c-.27.28-.59.54-.96.76-.36.22-.71.43-1.05.64-.33.2-1.02.47-2.05.79-1.03.32-2.03.49-2.99.49s-1.93-.13-2.91-.38c-.98-.25-1.99-.68-3.03-1.27-1.04-.6-1.98-1.32-2.81-2.18-.83-.86-1.51-1.96-2.05-3.31-.54-1.35-.8-2.81-.8-4.38s.26-3.01.79-4.29c.53-1.28,1.2-2.35,2.02-3.19.82-.84,1.75-1.54,2.81-2.11,1.98-1.09,3.97-1.64,5.98-1.64.95,0,1.92.15,2.9.44.98.29,1.72.59,2.23.9l.73.42c.36.22.65.4.85.55.53.42.79.91.79,1.44s-.21,1.1-.64,1.68c-.79,1.09-1.5,1.64-2.12,1.64-.36,0-.88-.22-1.55-.67-.85-.69-1.98-1.03-3.4-1.03-1.31,0-2.61.46-3.88,1.36-.61.44-1.11,1.07-1.52,1.88-.4.81-.61,1.72-.61,2.75s.2,1.94.61,2.75c.4.81.92,1.45,1.55,1.91,1.23.89,2.52,1.34,3.85,1.34.63,0,1.22-.08,1.77-.24.56-.16.96-.32,1.2-.49Z"/>
|
|
||||||
<path class="cls-3" d="M114.38,9.07c.16-.3.43-.52.82-.64.38-.12.87-.18,1.46-.18s1.05.05,1.4.15c.34.1.61.22.79.36.18.14.32.34.42.61.1.34.15.87.15,1.58v16.84c0,.47-.02.81-.05,1.05-.03.23-.13.5-.29.8-.28.55-1.07.82-2.37.82-1.42,0-2.25-.37-2.49-1.12-.12-.34-.18-.87-.18-1.58v-6.16h-8.04v6.19c0,.47-.02.81-.05,1.05-.03.23-.13.5-.29.8-.28.55-1.07.82-2.37.82-1.42,0-2.25-.37-2.49-1.12-.12-.34-.18-.87-.18-1.58V10.92c0-.46.02-.81.05-1.05.03-.23.13-.5.29-.8.28-.55,1.07-.82,2.37-.82,1.42,0,2.25.37,2.52,1.12.1.34.15.87.15,1.58v6.19h8.04v-6.22c0-.46.02-.81.05-1.05.03-.23.13-.5.29-.8Z"/>
|
|
||||||
<path class="cls-3" d="M127.21,25.1h9.34c.47,0,.81.02,1.05.05.23.03.5.13.8.29.55.28.82,1.07.82,2.37,0,1.42-.37,2.25-1.12,2.49-.34.12-.87.18-1.58.18h-12.01c-1.42,0-2.25-.38-2.49-1.15-.12-.32-.18-.84-.18-1.55V10.9c0-1.03.19-1.73.58-2.11.38-.37,1.11-.56,2.18-.56h11.95c.47,0,.81.02,1.05.05.23.03.5.13.8.29.55.28.82,1.07.82,2.37,0,1.42-.37,2.25-1.12,2.49-.34.12-.87.18-1.58.18h-9.31v3.06h6.01c.46,0,.81.02,1.05.05.23.03.5.13.8.29.55.28.82,1.07.82,2.37,0,1.42-.38,2.25-1.15,2.49-.34.12-.87.18-1.58.18h-5.95v3.06Z"/>
|
|
||||||
<path class="cls-3" d="M196.96,8.79c.99.69,1.49,1.35,1.49,2,0,.38-.23.92-.7,1.61l-6.55,9.8v5.79c0,.47-.02.81-.05,1.05-.03.23-.13.5-.29.8-.16.3-.43.52-.82.64-.38.12-.9.18-1.55.18s-1.16-.06-1.55-.18c-.38-.12-.66-.34-.82-.65-.16-.31-.26-.59-.29-.82-.03-.23-.05-.59-.05-1.08v-5.73l-6.55-9.8c-.47-.69-.7-1.22-.7-1.61,0-.65.44-1.27,1.33-1.87.89-.6,1.53-.9,1.91-.9s.69.08.91.24c.34.22.71.64,1.09,1.24l4.7,7.52,4.7-7.52c.38-.61.72-1.01,1-1.2s.61-.29.99-.29.97.25,1.77.76Z"/>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M81.93,56.63c-.53-.65-.79-1.23-.79-1.74s.43-1.2,1.3-2.05c.51-.49,1.04-.73,1.61-.73s1.36.51,2.37,1.52c.28.34.69.67,1.21.99.53.31,1.01.47,1.46.47,1.88,0,2.82-.77,2.82-2.31,0-.46-.26-.85-.77-1.17-.52-.31-1.16-.54-1.93-.68-.77-.14-1.6-.37-2.49-.68-.89-.31-1.72-.68-2.49-1.11-.77-.42-1.41-1.1-1.93-2.02-.52-.92-.77-2.03-.77-3.32,0-1.78.66-3.33,1.99-4.66s3.13-1.99,5.42-1.99c1.21,0,2.32.16,3.32.47,1,.31,1.69.63,2.08.96l.76.58c.63.59.94,1.08.94,1.49s-.24.96-.73,1.67c-.69,1.01-1.4,1.52-2.12,1.52-.42,0-.95-.2-1.58-.61-.06-.04-.18-.14-.35-.3-.17-.16-.33-.29-.47-.39-.42-.26-.97-.39-1.62-.39s-1.2.16-1.64.47c-.43.31-.65.75-.65,1.3s.26,1.01.77,1.35c.52.34,1.16.58,1.93.7.77.12,1.61.31,2.52.56.91.25,1.75.56,2.52.93.77.36,1.41,1,1.93,1.9.52.9.77,2.01.77,3.32s-.26,2.47-.79,3.47c-.53,1-1.21,1.77-2.06,2.32-1.64,1.07-3.39,1.61-5.25,1.61-.95,0-1.85-.12-2.7-.35-.85-.23-1.54-.52-2.06-.86-1.07-.65-1.82-1.27-2.24-1.88l-.27-.33Z"/>
|
|
||||||
<path class="cls-3" d="M100.74,37.49h16.87c.65,0,1.12.08,1.43.23.3.15.51.39.61.71.1.32.15.75.15,1.27s-.05.95-.15,1.26c-.1.31-.27.53-.52.65-.36.18-.88.27-1.55.27h-5.79v15.26c0,.47-.02.81-.05,1.03s-.12.48-.27.77c-.15.29-.42.5-.8.62-.38.12-.89.18-1.52.18s-1.13-.06-1.5-.18c-.37-.12-.64-.33-.79-.62-.15-.29-.24-.56-.27-.79-.03-.23-.05-.58-.05-1.05v-15.23h-5.82c-.65,0-1.12-.08-1.43-.23-.3-.15-.51-.39-.61-.71-.1-.32-.15-.75-.15-1.27s.05-.95.15-1.26c.1-.31.27-.53.52-.65.36-.18.88-.27,1.55-.27Z"/>
|
|
||||||
<path class="cls-3" d="M135.99,38.34c.2-.32.5-.55.88-.67.38-.12.86-.18,1.44-.18s1.04.05,1.38.15c.34.1.61.22.79.36.18.14.31.35.39.64.12.34.18.87.18,1.58v9.16c0,2.67-.83,5.1-2.49,7.28-.81,1.03-1.85,1.87-3.12,2.5s-2.68.96-4.23.96-2.95-.32-4.22-.97c-1.26-.65-2.29-1.5-3.08-2.55-1.64-2.14-2.46-4.57-2.46-7.28v-9.13c0-.49.02-.84.05-1.08.03-.23.13-.5.29-.8.16-.3.43-.52.82-.64.38-.12.9-.18,1.55-.18s1.16.06,1.55.18c.38.12.65.33.79.64.24.47.36,1.1.36,1.91v9.1c0,1.23.3,2.41.91,3.52.3.57.76,1.02,1.37,1.36.61.34,1.32.52,2.15.52,1.48,0,2.58-.55,3.31-1.64.73-1.09,1.09-2.36,1.09-3.79v-9.28c0-.79.1-1.34.3-1.67Z"/>
|
|
||||||
<path class="cls-3" d="M146.18,37.49l5.61.03c2.93,0,5.51,1.06,7.74,3.17,2.22,2.11,3.34,4.71,3.34,7.8s-1.09,5.73-3.26,7.93c-2.17,2.2-4.81,3.31-7.9,3.31h-5.55c-1.23,0-2-.25-2.31-.76-.24-.42-.36-1.07-.36-1.94v-16.87c0-.49.02-.84.05-1.06s.13-.49.29-.79c.28-.55,1.07-.82,2.37-.82ZM151.79,54.35c1.46,0,2.77-.54,3.94-1.62,1.17-1.08,1.76-2.44,1.76-4.08s-.57-3.01-1.71-4.11c-1.14-1.1-2.48-1.65-4.02-1.65h-2.91v11.47h2.94Z"/>
|
|
||||||
<path class="cls-3" d="M164.84,40.19c0-.46.02-.81.05-1.05.03-.23.13-.5.29-.8.28-.55,1.07-.82,2.37-.82,1.42,0,2.25.37,2.52,1.12.1.34.15.87.15,1.58v16.87c0,.49-.02.84-.05,1.06s-.13.49-.29.79c-.28.55-1.07.82-2.37.82-1.42,0-2.25-.38-2.49-1.15-.12-.32-.18-.84-.18-1.55v-16.87Z"/>
|
|
||||||
<path class="cls-3" d="M183.07,37.24c2.99,0,5.59,1.08,7.8,3.25,2.2,2.16,3.31,4.85,3.31,8.05s-1.05,5.94-3.16,8.19c-2.1,2.26-4.69,3.38-7.77,3.38s-5.69-1.11-7.84-3.34c-2.15-2.22-3.23-4.87-3.23-7.95,0-1.68.3-3.25.91-4.72.61-1.47,1.42-2.7,2.43-3.69,1.01-.99,2.17-1.77,3.49-2.34,1.31-.57,2.67-.85,4.07-.85ZM177.55,48.68c0,1.8.58,3.26,1.74,4.38,1.16,1.12,2.46,1.68,3.9,1.68s2.73-.55,3.88-1.64c1.15-1.09,1.73-2.56,1.73-4.4s-.58-3.32-1.74-4.43c-1.16-1.11-2.46-1.67-3.9-1.67s-2.73.56-3.88,1.68c-1.15,1.12-1.73,2.58-1.73,4.38Z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M176.92,11.06c-.03-.23-.13-.5-.29-.8-.28-.55-1.07-.82-2.37-.82h-6.55c-1.78,0-3.51.65-5.19,1.94-.81.63-1.48,1.48-2,2.55-.53,1.07-.79,2.27-.79,3.58,0,2.29.76,4.17,2.28,5.64-.44,1.07-1.13,2.66-2.06,4.76-.3.73-.45,1.25-.45,1.58,0,.77.63,1.42,1.88,1.94.65.28,1.17.43,1.56.43s.72-.1.97-.29c.25-.19.44-.39.56-.59.2-.38.99-2.21,2.37-5.49l.94.06h3.82v3.43c0,.47.02.81.05,1.05.03.23.13.5.29.8.28.55,1.07.82,2.37.82,1.42,0,2.25-.37,2.49-1.12.12-.34.18-.87.18-1.58V12.11c0-.46-.02-.81-.05-1.05ZM172.81,19.44c-.09.14-.48.77-1.24.91-.2.04-.37.03-.48.02-.02.14-.04.26-.06.38-.16.83-.38,1.05-.57,1.07-.29.05-.51-.35-.93-.9-.23.01-.46.02-.69.02-.51,0-1.01-.03-1.49-.09-.25-.03-.5-.07-.74-.11-1.18-.32-2.03-1.27-2.03-2.4v-1.37c0-1.13.86-2.08,2.03-2.4.24-.04.49-.08.74-.11.48-.06.98-.09,1.49-.09s1.01.03,1.49.09c.25.03.5.07.74.11.6.16,1.12.49,1.49.93.34.41.55.92.55,1.47v1.37c0,.23-.01.66-.29,1.1Z"/>
|
|
||||||
<circle class="cls-2" cx="167.24" cy="17.67" r=".49"/>
|
|
||||||
<circle class="cls-2" cx="168.88" cy="17.71" r=".49"/>
|
|
||||||
<circle class="cls-2" cx="170.59" cy="17.71" r=".49"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M141.01,8.24c.03-.23.13-.5.29-.8.28-.55,1.07-.82,2.37-.82h6.55c1.78,0,3.51.65,5.19,1.94.81.63,1.48,1.48,2,2.55.53,1.07.79,2.27.79,3.58,0,2.29-.76,4.17-2.28,5.64.44,1.07,1.13,2.66,2.06,4.76.3.73.45,1.25.45,1.58,0,.77-.63,1.42-1.88,1.94-.65.28-1.17.43-1.56.43s-.72-.1-.97-.29c-.25-.19-.44-.39-.56-.59-.2-.38-.99-2.21-2.37-5.49l-.94.06h-3.82v3.43c0,.47-.02.81-.05,1.05-.03.23-.13.5-.29.8-.28.55-1.07.82-2.37.82-1.42,0-2.25-.37-2.49-1.12-.12-.34-.18-.87-.18-1.58V9.28c0-.46.02-.81.05-1.05ZM145.12,16.62c.09.14.48.77,1.24.91.2.04.37.03.48.02.02.14.04.26.06.38.16.83.38,1.05.57,1.07.29.05.51-.35.93-.9.23.01.46.02.69.02.51,0,1.01-.03,1.49-.09.25-.03.5-.07.74-.11,1.18-.32,2.03-1.27,2.03-2.4v-1.37c0-1.13-.86-2.08-2.03-2.4-.24-.04-.49-.08-.74-.11-.48-.06-.98-.09-1.49-.09s-1.01.03-1.49.09c-.25.03-.5.07-.74.11-.6.16-1.12.49-1.49.93-.34.41-.55.92-.55,1.47v1.37c0,.23.01.66.29,1.1Z"/>
|
|
||||||
<circle class="cls-2" cx="150.69" cy="14.84" r=".49"/>
|
|
||||||
<circle class="cls-2" cx="149.05" cy="14.89" r=".49"/>
|
|
||||||
<circle class="cls-2" cx="147.35" cy="14.89" r=".49"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 9.5 KiB |
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="_图层_2" data-name="图层_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.39 115.44">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #ea5e5d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #23af69;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-3 {
|
|
||||||
fill: #ea5756;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g id="_图层_1-2" data-name="图层_1">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-1" d="M25.31,51.21c-4.45,0-8.64-1.78-11.81-5.01-3.17-3.23-4.91-7.51-4.91-12.04s1.74-8.81,4.91-12.04,7.36-5.01,11.81-5.01,8.71,1.82,11.82,4.99c2.32,2.36,2.32,6.2,0,8.56-2.32,2.36-6.08,2.36-8.4,0-.9-.92-2.15-1.45-3.43-1.45-2.63,0-4.85,2.26-4.85,4.94s2.22,4.94,4.85,4.94c1.28,0,2.52-.53,3.43-1.45,2.32-2.36,6.08-2.36,8.4,0,2.32,2.36,2.32,6.2,0,8.56-3.11,3.17-7.42,4.99-11.82,4.99Z"/>
|
|
||||||
<path class="cls-1" d="M40.64,66.73c-4.45,0-8.64-1.78-11.81-5.01s-4.91-7.51-4.91-12.04,1.79-8.88,4.9-12.06c2.32-2.36,6.08-2.36,8.4,0,2.32,2.36,2.32,6.2,0,8.56-.9.92-1.42,2.19-1.42,3.49,0,2.68,2.22,4.94,4.85,4.94s4.85-2.26,4.85-4.94c0-.95-.23-2.31-1.32-3.43-3.13-3.19-4.92-7.6-4.92-12.09s1.74-8.81,4.91-12.04c3.17-3.23,7.36-5.01,11.81-5.01s8.64,1.78,11.81,5.01,4.91,7.51,4.91,12.04-1.79,8.88-4.9,12.06c-2.32,2.36-6.08,2.36-8.4,0-2.32-2.36-2.32-6.2,0-8.56.9-.92,1.42-2.19,1.42-3.49,0-2.68-2.22-4.94-4.85-4.94s-4.85,2.26-4.85,4.94c0,1.31.53,2.6,1.45,3.53,3.1,3.16,4.8,7.42,4.8,11.99s-1.74,8.81-4.91,12.04c-3.17,3.23-7.36,5.01-11.81,5.01Z"/>
|
|
||||||
</g>
|
|
||||||
<path class="cls-2" d="M40.64,19.09l-9.72-9.12c-1.5-1.4-1.57-3.75-.17-5.25,1.4-1.49,3.75-1.57,5.25-.17l3.89,3.65,5.53-6.83c1.29-1.59,3.63-1.84,5.22-.55,1.59,1.29,1.84,3.63.55,5.22l-10.56,13.05Z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M10.19,90.22l.39-.28c.49-.29.83-.43,1.03-.43.45,0,.93.4,1.44,1.21.32.5.47.9.47,1.21s-.1.55-.29.75c-.19.2-.42.38-.68.54-.26.16-.51.31-.74.45-.24.14-.72.33-1.45.56-.73.23-1.44.34-2.12.34s-1.37-.09-2.07-.27c-.7-.18-1.41-.48-2.15-.9-.74-.42-1.4-.94-1.99-1.55-.59-.61-1.07-1.39-1.45-2.35-.38-.96-.57-1.99-.57-3.11s.19-2.14.56-3.05c.37-.91.85-1.67,1.43-2.26.58-.6,1.25-1.09,1.99-1.5,1.41-.78,2.82-1.16,4.24-1.16.67,0,1.36.1,2.06.31.7.21,1.22.42,1.58.64l.52.3c.26.16.46.29.6.39.37.3.56.64.56,1.02s-.15.78-.45,1.2c-.56.78-1.06,1.16-1.51,1.16-.26,0-.62-.16-1.1-.47-.6-.49-1.41-.73-2.41-.73-.93,0-1.85.32-2.76.97-.43.32-.79.76-1.08,1.34-.29.57-.43,1.22-.43,1.95s.14,1.37.43,1.95c.29.57.65,1.03,1.1,1.36.88.63,1.79.95,2.74.95.45,0,.87-.06,1.26-.17.39-.11.68-.23.85-.34Z"/>
|
|
||||||
<path class="cls-3" d="M24.7,79.2c.11-.22.31-.37.58-.45.27-.09.62-.13,1.03-.13s.75.04.99.11c.24.07.43.16.56.26.13.1.23.24.3.43.07.24.11.62.11,1.12v11.95c0,.33-.01.58-.03.74-.02.17-.09.36-.2.57-.2.39-.76.58-1.68.58-1.01,0-1.59-.27-1.77-.8-.09-.24-.13-.62-.13-1.12v-4.37h-5.71v4.39c0,.33-.01.58-.03.74-.02.17-.09.36-.2.57-.2.39-.76.58-1.68.58-1.01,0-1.59-.27-1.77-.8-.09-.24-.13-.62-.13-1.12v-11.95c0-.33.01-.58.03-.74.02-.17.09-.36.2-.57.2-.39.76-.58,1.68-.58,1.01,0,1.6.27,1.79.8.07.24.11.62.11,1.12v4.39h5.71v-4.42c0-.33.01-.58.03-.74.02-.17.09-.36.2-.57Z"/>
|
|
||||||
<path class="cls-3" d="M33.82,90.58h6.63c.33,0,.58.01.74.03.17.02.36.09.57.2.39.2.58.76.58,1.68,0,1.01-.27,1.59-.8,1.77-.24.09-.62.13-1.12.13h-8.53c-1.01,0-1.59-.27-1.77-.82-.09-.23-.13-.6-.13-1.1v-11.98c0-.73.14-1.23.41-1.5.27-.27.79-.4,1.55-.4h8.49c.33,0,.58.01.74.03.17.02.36.09.57.2.39.2.58.76.58,1.68,0,1.01-.27,1.59-.8,1.77-.24.09-.62.13-1.12.13h-6.61v2.18h4.26c.33,0,.58.01.74.03.17.02.36.09.57.2.39.2.58.76.58,1.68,0,1.01-.27,1.59-.82,1.77-.24.09-.62.13-1.12.13h-4.22v2.18Z"/>
|
|
||||||
<path class="cls-3" d="M83.34,79c.7.49,1.06.96,1.06,1.42,0,.27-.17.65-.5,1.14l-4.65,6.96v4.11c0,.33-.01.58-.03.74-.02.17-.09.36-.2.57-.11.22-.31.37-.58.45-.27.09-.64.13-1.1.13s-.83-.04-1.1-.13c-.27-.09-.47-.24-.58-.46-.11-.22-.18-.42-.2-.58-.02-.17-.03-.42-.03-.76v-4.07l-4.65-6.96c-.33-.49-.5-.87-.5-1.14,0-.46.32-.9.95-1.32.63-.42,1.08-.64,1.36-.64s.49.06.65.17c.24.16.5.45.78.88l3.34,5.34,3.34-5.34c.27-.43.51-.71.71-.85s.43-.2.7-.2.69.18,1.26.54Z"/>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M1.66,112.96c-.37-.46-.56-.87-.56-1.24s.31-.85.93-1.45c.36-.34.74-.52,1.14-.52s.96.36,1.68,1.08c.2.24.49.48.86.7.37.22.72.33,1.03.33,1.34,0,2-.55,2-1.64,0-.33-.18-.61-.55-.83-.37-.22-.82-.38-1.37-.48-.55-.1-1.13-.26-1.77-.48-.63-.22-1.22-.48-1.77-.79-.55-.3-1-.78-1.37-1.43-.37-.65-.55-1.44-.55-2.36,0-1.26.47-2.37,1.41-3.31s2.22-1.41,3.84-1.41c.86,0,1.65.11,2.36.33.71.22,1.2.45,1.48.68l.54.41c.45.42.67.77.67,1.06s-.17.68-.52,1.18c-.49.72-.99,1.08-1.51,1.08-.3,0-.67-.14-1.12-.43-.04-.03-.13-.1-.25-.22-.12-.11-.23-.21-.33-.28-.3-.19-.69-.28-1.15-.28s-.85.11-1.16.33c-.31.22-.46.53-.46.93s.18.71.55.96c.37.24.82.41,1.37.5.55.09,1.14.22,1.79.4.65.18,1.24.4,1.79.66.55.26,1,.71,1.37,1.35.37.64.55,1.42.55,2.36s-.19,1.76-.56,2.47c-.37.71-.86,1.26-1.46,1.65-1.16.76-2.4,1.14-3.73,1.14-.68,0-1.31-.08-1.92-.25-.6-.17-1.09-.37-1.46-.61-.76-.46-1.29-.9-1.59-1.34l-.19-.24Z"/>
|
|
||||||
<path class="cls-3" d="M15.02,99.37h11.98c.46,0,.8.05,1.01.16.22.11.36.28.43.51.07.23.11.53.11.9s-.04.67-.11.89c-.07.22-.19.38-.37.46-.26.13-.62.19-1.1.19h-4.11v10.83c0,.33-.01.57-.03.73s-.09.34-.19.55c-.11.21-.3.36-.57.44-.27.09-.63.13-1.08.13s-.8-.04-1.07-.13c-.27-.09-.45-.23-.56-.44-.11-.21-.17-.4-.19-.56-.02-.17-.03-.41-.03-.74v-10.81h-4.14c-.46,0-.8-.05-1.01-.16-.22-.11-.36-.28-.43-.51-.07-.23-.11-.53-.11-.9s.04-.67.11-.89c.07-.22.19-.38.37-.46.26-.13.62-.19,1.1-.19Z"/>
|
|
||||||
<path class="cls-3" d="M40.05,99.98c.14-.23.35-.39.62-.47.27-.09.61-.13,1.02-.13s.74.04.98.11c.24.07.43.16.56.26.13.1.22.25.28.45.09.24.13.62.13,1.12v6.5c0,1.9-.59,3.62-1.77,5.17-.57.73-1.31,1.32-2.22,1.78s-1.91.68-3,.68-2.1-.23-2.99-.69c-.9-.46-1.63-1.06-2.19-1.81-1.16-1.52-1.74-3.25-1.74-5.17v-6.48c0-.34.01-.6.03-.76.02-.17.09-.36.2-.57.11-.22.31-.37.58-.45.27-.09.64-.13,1.1-.13s.83.04,1.1.13c.27.09.46.24.56.45.17.33.26.78.26,1.36v6.46c0,.88.22,1.71.65,2.5.22.4.54.72.97.97.43.24.94.37,1.53.37,1.05,0,1.83-.39,2.35-1.16.52-.78.78-1.67.78-2.69v-6.59c0-.56.07-.95.22-1.18Z"/>
|
|
||||||
<path class="cls-3" d="M47.28,99.37l3.98.02c2.08,0,3.91.75,5.49,2.25,1.58,1.5,2.37,3.35,2.37,5.54s-.77,4.07-2.32,5.63c-1.54,1.57-3.41,2.35-5.61,2.35h-3.94c-.88,0-1.42-.18-1.64-.54-.17-.3-.26-.76-.26-1.38v-11.98c0-.34.01-.6.03-.75s.09-.34.2-.56c.2-.39.76-.58,1.68-.58ZM51.27,111.35c1.03,0,1.97-.38,2.8-1.15.83-.77,1.25-1.73,1.25-2.9s-.41-2.14-1.22-2.92c-.81-.78-1.76-1.17-2.85-1.17h-2.07v8.14h2.09Z"/>
|
|
||||||
<path class="cls-3" d="M60.53,101.29c0-.33.01-.58.03-.74.02-.17.09-.36.2-.57.2-.39.76-.58,1.68-.58,1.01,0,1.6.27,1.79.8.07.24.11.62.11,1.12v11.98c0,.34-.01.6-.03.75s-.09.34-.2.56c-.2.39-.76.58-1.68.58-1.01,0-1.59-.27-1.77-.82-.09-.23-.13-.6-.13-1.1v-11.98Z"/>
|
|
||||||
<path class="cls-3" d="M73.47,99.2c2.13,0,3.97.77,5.54,2.3,1.57,1.54,2.35,3.44,2.35,5.72s-.75,4.21-2.24,5.82c-1.49,1.6-3.33,2.4-5.51,2.4s-4.04-.79-5.57-2.37c-1.53-1.58-2.29-3.46-2.29-5.64,0-1.19.22-2.31.65-3.35.43-1.04,1.01-1.91,1.72-2.62.72-.7,1.54-1.26,2.48-1.66.93-.4,1.9-.6,2.89-.6ZM69.55,107.32c0,1.28.41,2.32,1.24,3.11.83.8,1.75,1.2,2.77,1.2s1.94-.39,2.76-1.16c.82-.78,1.23-1.82,1.23-3.12s-.41-2.35-1.24-3.14c-.83-.79-1.75-1.18-2.77-1.18s-1.94.4-2.76,1.2c-.82.8-1.23,1.83-1.23,3.11Z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M69.11,80.61c-.02-.17-.09-.36-.2-.57-.2-.39-.76-.58-1.68-.58h-4.65c-1.26,0-2.49.46-3.68,1.38-.57.45-1.05,1.05-1.42,1.81-.37.76-.56,1.61-.56,2.54,0,1.62.54,2.96,1.62,4.01-.32.76-.8,1.89-1.46,3.38-.22.52-.32.89-.32,1.12,0,.55.45,1.01,1.34,1.38.46.2.83.3,1.11.3s.51-.07.69-.2c.18-.14.31-.28.4-.42.14-.27.7-1.57,1.68-3.9l.67.04h2.71v2.43c0,.33.01.58.03.74.02.17.09.36.2.57.2.39.76.58,1.68.58,1.01,0,1.59-.27,1.77-.8.09-.24.13-.62.13-1.12v-11.95c0-.33-.01-.58-.03-.74ZM66.19,86.56c-.06.1-.34.54-.88.65-.14.03-.26.02-.34.02-.01.1-.03.19-.04.27-.11.59-.27.74-.4.76-.21.03-.36-.25-.66-.64-.16,0-.32.01-.49.01-.36,0-.72-.02-1.06-.06-.18-.02-.35-.05-.52-.08-.84-.22-1.44-.9-1.44-1.7v-.97c0-.8.61-1.48,1.44-1.7.17-.03.34-.06.52-.08.34-.04.69-.06,1.06-.06s.72.02,1.06.06c.18.02.35.05.52.08.43.12.8.35,1.06.66.24.29.39.65.39,1.05v.97c0,.16,0,.47-.21.78Z"/>
|
|
||||||
<circle class="cls-2" cx="62.23" cy="85.3" r=".35"/>
|
|
||||||
<circle class="cls-2" cx="63.4" cy="85.33" r=".35"/>
|
|
||||||
<circle class="cls-2" cx="64.61" cy="85.33" r=".35"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-3" d="M43.62,78.61c.02-.17.09-.36.2-.57.2-.39.76-.58,1.68-.58h4.65c1.26,0,2.49.46,3.68,1.38.57.45,1.05,1.05,1.42,1.81.37.76.56,1.61.56,2.54,0,1.62-.54,2.96-1.62,4.01.32.76.8,1.89,1.46,3.38.22.52.32.89.32,1.12,0,.55-.45,1.01-1.34,1.38-.46.2-.83.3-1.11.3s-.51-.07-.69-.2c-.18-.14-.31-.28-.4-.42-.14-.27-.7-1.57-1.68-3.9l-.67.04h-2.71v2.43c0,.33-.01.58-.03.74-.02.17-.09.36-.2.57-.2.39-.76.58-1.68.58-1.01,0-1.59-.27-1.77-.8-.09-.24-.13-.62-.13-1.12v-11.95c0-.33.01-.58.03-.74ZM46.53,84.56c.06.1.34.54.88.65.14.03.26.02.34.02.01.1.03.19.04.27.11.59.27.74.4.76.21.03.36-.25.66-.64.16,0,.32.01.49.01.36,0,.72-.02,1.06-.06.18-.02.35-.05.52-.08.84-.22,1.44-.9,1.44-1.7v-.97c0-.8-.61-1.48-1.44-1.7-.17-.03-.34-.06-.52-.08-.34-.04-.69-.06-1.06-.06s-.72.02-1.06.06c-.18.02-.35.05-.52.08-.43.12-.8.35-1.06.66-.24.29-.39.65-.39,1.05v.97c0,.16,0,.47.21.78Z"/>
|
|
||||||
<circle class="cls-2" cx="50.49" cy="83.3" r=".35"/>
|
|
||||||
<circle class="cls-2" cx="49.32" cy="83.33" r=".35"/>
|
|
||||||
<circle class="cls-2" cx="48.11" cy="83.33" r=".35"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 9.2 KiB |
BIN
src/renderer/src/assets/images/models/bigcode.webp
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/renderer/src/assets/images/models/bigcode_dark.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
@@ -43,6 +43,10 @@
|
|||||||
--color-frame-border: #333;
|
--color-frame-border: #333;
|
||||||
--color-group-background: var(--color-background-soft);
|
--color-group-background: var(--color-background-soft);
|
||||||
|
|
||||||
|
--color-reference: #404040;
|
||||||
|
--color-reference-text: #ffffff;
|
||||||
|
--color-reference-background: #0b0e12;
|
||||||
|
|
||||||
--navbar-background-mac: rgba(30, 30, 30, 0.6);
|
--navbar-background-mac: rgba(30, 30, 30, 0.6);
|
||||||
--navbar-background: rgba(30, 30, 30);
|
--navbar-background: rgba(30, 30, 30);
|
||||||
|
|
||||||
@@ -59,6 +63,8 @@
|
|||||||
--chat-background-user: #28b561;
|
--chat-background-user: #28b561;
|
||||||
--chat-background-assistant: #2c2c2c;
|
--chat-background-assistant: #2c2c2c;
|
||||||
--chat-text-user: var(--color-black);
|
--chat-text-user: var(--color-black);
|
||||||
|
|
||||||
|
--list-item-border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme-mode='light'] {
|
body[theme-mode='light'] {
|
||||||
@@ -100,6 +106,10 @@ body[theme-mode='light'] {
|
|||||||
--color-frame-border: #ddd;
|
--color-frame-border: #ddd;
|
||||||
--color-group-background: var(--color-white);
|
--color-group-background: var(--color-white);
|
||||||
|
|
||||||
|
--color-reference: #cfe1ff;
|
||||||
|
--color-reference-text: #000000;
|
||||||
|
--color-reference-background: #f1f7ff;
|
||||||
|
|
||||||
--navbar-background-mac: rgba(255, 255, 255, 0.6);
|
--navbar-background-mac: rgba(255, 255, 255, 0.6);
|
||||||
--navbar-background: rgba(255, 255, 255);
|
--navbar-background: rgba(255, 255, 255);
|
||||||
|
|
||||||
@@ -167,21 +177,9 @@ body,
|
|||||||
#content-container {
|
#content-container {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
border-top: 0.5px solid var(--color-border);
|
border-top: 0.5px solid var(--color-border);
|
||||||
}
|
border-top-left-radius: 10px;
|
||||||
|
border-left: 0.5px solid var(--color-border);
|
||||||
body[os='mac'] {
|
box-shadow: -2px 0px 20px -4px rgba(0, 0, 0, 0.06);
|
||||||
#content-container {
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-bottom-left-radius: 10px;
|
|
||||||
border-left: 0.5px solid var(--color-border);
|
|
||||||
box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body[os='windows'] {
|
|
||||||
#app-sidebar {
|
|
||||||
border-right: 0.5px solid var(--color-border);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
@@ -223,10 +221,7 @@ body[os='windows'] {
|
|||||||
background-color: var(--chat-background);
|
background-color: var(--chat-background);
|
||||||
}
|
}
|
||||||
#inputbar {
|
#inputbar {
|
||||||
border-radius: 0;
|
margin: -5px 15px 15px 15px;
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid var(--color-border-mute);
|
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
.system-prompt {
|
.system-prompt {
|
||||||
|
|||||||
@@ -66,6 +66,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: initial;
|
||||||
|
}
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
@@ -204,6 +208,14 @@
|
|||||||
|
|
||||||
sup {
|
sup {
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-reference);
|
||||||
|
color: var(--color-reference-text);
|
||||||
|
padding: 2px 5px;
|
||||||
|
zoom: 0.8;
|
||||||
|
& > span.link {
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub {
|
sub {
|
||||||
@@ -222,38 +234,55 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.footnotes {
|
.footnotes {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
padding-top: 1em;
|
margin-bottom: 1em;
|
||||||
border-top: 1px solid var(--color-border);
|
padding-top: 1em;
|
||||||
|
|
||||||
ol {
|
background-color: var(--color-reference-background);
|
||||||
padding-left: 1em;
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
padding-left: 1em;
|
||||||
|
margin: 0;
|
||||||
|
li:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
|
|
||||||
p {
|
p {
|
||||||
display: inline;
|
display: inline;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.footnote-backref {
|
.footnote-backref {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
vertical-align: super;
|
vertical-align: super;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ body[theme-mode='light'] {
|
|||||||
|
|
||||||
/* 全局初始化滚动条样式 */
|
/* 全局初始化滚动条样式 */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 6px;
|
||||||
height: 5px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
|
||||||
import { HStack } from '@renderer/components/Layout'
|
|
||||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
|
||||||
import { SettingRow } from '@renderer/pages/settings'
|
|
||||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
|
||||||
import { Button, Col, Divider, Row, Slider, Switch, Tooltip } from 'antd'
|
|
||||||
import { FC, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
import ModelAvatar from '../Avatar/ModelAvatar'
|
|
||||||
import SelectModelPopup from '../Popups/SelectModelPopup'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
assistant: Assistant
|
|
||||||
updateAssistant: (assistant: Assistant) => void
|
|
||||||
updateAssistantSettings: (settings: Partial<AssistantSettings>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateAssistantSettings }) => {
|
|
||||||
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
|
|
||||||
const [contextCount, setContextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONTEXTCOUNT)
|
|
||||||
const [enableMaxTokens, setEnableMaxTokens] = useState(assistant?.settings?.enableMaxTokens ?? false)
|
|
||||||
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 [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const onTemperatureChange = (value) => {
|
|
||||||
if (!isNaN(value as number)) {
|
|
||||||
updateAssistantSettings({ temperature: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onContextCountChange = (value) => {
|
|
||||||
if (!isNaN(value as number)) {
|
|
||||||
updateAssistantSettings({ contextCount: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMaxTokensChange = (value) => {
|
|
||||||
if (!isNaN(value as number)) {
|
|
||||||
updateAssistantSettings({ maxTokens: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onReset = () => {
|
|
||||||
setTemperature(DEFAULT_TEMPERATURE)
|
|
||||||
setContextCount(DEFAULT_CONTEXTCOUNT)
|
|
||||||
setEnableMaxTokens(false)
|
|
||||||
setMaxTokens(0)
|
|
||||||
setStreamOutput(true)
|
|
||||||
updateAssistantSettings({
|
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
|
||||||
contextCount: DEFAULT_CONTEXTCOUNT,
|
|
||||||
enableMaxTokens: false,
|
|
||||||
maxTokens: 0,
|
|
||||||
streamOutput: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSelectModel = async () => {
|
|
||||||
const selectedModel = await SelectModelPopup.show({ model: assistant?.model })
|
|
||||||
if (selectedModel) {
|
|
||||||
setDefaultModel(selectedModel)
|
|
||||||
updateAssistant({
|
|
||||||
...assistant,
|
|
||||||
defaultModel: selectedModel
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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">
|
|
||||||
<Label>{t('chat.settings.temperature')}</Label>
|
|
||||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
|
||||||
<QuestionIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle" gutter={10}>
|
|
||||||
<Col span={24}>
|
|
||||||
<Slider
|
|
||||||
min={0}
|
|
||||||
max={2}
|
|
||||||
onChange={setTemperature}
|
|
||||||
onChangeComplete={onTemperatureChange}
|
|
||||||
value={typeof temperature === 'number' ? temperature : 0}
|
|
||||||
step={0.1}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle">
|
|
||||||
<Label>
|
|
||||||
{t('chat.settings.context_count')}{' '}
|
|
||||||
<Tooltip title={t('chat.settings.context_count.tip')}>
|
|
||||||
<QuestionIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</Label>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle" gutter={10}>
|
|
||||||
<Col span={24}>
|
|
||||||
<Slider
|
|
||||||
min={0}
|
|
||||||
max={20}
|
|
||||||
onChange={setContextCount}
|
|
||||||
onChangeComplete={onContextCountChange}
|
|
||||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
|
||||||
step={1}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle" justify="space-between">
|
|
||||||
<HStack alignItems="center">
|
|
||||||
<Label>{t('chat.settings.max_tokens')}</Label>
|
|
||||||
<Tooltip title={t('chat.settings.max_tokens.tip')}>
|
|
||||||
<QuestionIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
<Switch
|
|
||||||
checked={enableMaxTokens}
|
|
||||||
onChange={(enabled) => {
|
|
||||||
setEnableMaxTokens(enabled)
|
|
||||||
updateAssistantSettings({ enableMaxTokens: enabled })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
<Row align="middle" gutter={10}>
|
|
||||||
<Col span={24}>
|
|
||||||
<Slider
|
|
||||||
disabled={!enableMaxTokens}
|
|
||||||
min={0}
|
|
||||||
max={32000}
|
|
||||||
onChange={setMaxTokens}
|
|
||||||
onChangeComplete={onMaxTokensChange}
|
|
||||||
value={typeof maxTokens === 'number' ? maxTokens : 0}
|
|
||||||
step={100}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<SettingRow>
|
|
||||||
<Label>{t('model.stream_output')}</Label>
|
|
||||||
<Switch
|
|
||||||
checked={streamOutput}
|
|
||||||
onChange={(checked) => {
|
|
||||||
setStreamOutput(checked)
|
|
||||||
updateAssistantSettings({ streamOutput: checked })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingRow>
|
|
||||||
<Divider style={{ margin: '15px 0' }} />
|
|
||||||
<HStack justifyContent="flex-end">
|
|
||||||
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
|
|
||||||
{t('chat.settings.reset')}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 5px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Label = styled.p`
|
|
||||||
margin-right: 5px;
|
|
||||||
font-weight: 500;
|
|
||||||
`
|
|
||||||
|
|
||||||
const QuestionIcon = styled(QuestionCircleOutlined)`
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
`
|
|
||||||
|
|
||||||
export default AssistantModelSettings
|
|
||||||
@@ -47,19 +47,22 @@ const DragableList: FC<Props<any>> = ({
|
|||||||
<Droppable droppableId="droppable" {...droppableProps}>
|
<Droppable droppableId="droppable" {...droppableProps}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div {...provided.droppableProps} ref={provided.innerRef} style={{ ...style }}>
|
<div {...provided.droppableProps} ref={provided.innerRef} style={{ ...style }}>
|
||||||
{list.map((item, index) => (
|
{list.map((item, index) => {
|
||||||
<Draggable key={`draggable_${item.id}_${index}`} draggableId={item.id} index={index} {...droppableProps}>
|
const id = item.id || item
|
||||||
{(provided) => (
|
return (
|
||||||
<div
|
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index} {...droppableProps}>
|
||||||
ref={provided.innerRef}
|
{(provided) => (
|
||||||
{...provided.draggableProps}
|
<div
|
||||||
{...provided.dragHandleProps}
|
ref={provided.innerRef}
|
||||||
style={{ ...provided.draggableProps.style, marginBottom: 8, ...listStyle }}>
|
{...provided.draggableProps}
|
||||||
{children(item, index)}
|
{...provided.dragHandleProps}
|
||||||
</div>
|
style={{ ...provided.draggableProps.style, marginBottom: 8, ...listStyle }}>
|
||||||
)}
|
{children(item, index)}
|
||||||
</Draggable>
|
</div>
|
||||||
))}
|
)}
|
||||||
|
</Draggable>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
|
|||||||
39
src/renderer/src/components/Icons/MinAppIcon.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||||
|
import { MinAppType } from '@renderer/types'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
app: MinAppType
|
||||||
|
size?: number
|
||||||
|
style?: React.CSSProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
const MinAppIcon: FC<Props> = ({ app, size = 48, style }) => {
|
||||||
|
const _app = DEFAULT_MIN_APPS.find((item) => item.id === app.id)
|
||||||
|
|
||||||
|
if (!_app) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
src={_app.logo}
|
||||||
|
style={{
|
||||||
|
border: _app.bodered ? '0.5px solid var(--color-border)' : 'none',
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
backgroundColor: _app.background,
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.img`
|
||||||
|
border-radius: 16px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default MinAppIcon
|
||||||
15
src/renderer/src/components/Icons/WebSearchIcon.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { GlobalOutlined } from '@ant-design/icons'
|
||||||
|
import React, { FC } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const WebSearchIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||||
|
return <Icon {...(props as any)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const Icon = styled(GlobalOutlined)`
|
||||||
|
color: var(--color-link);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default WebSearchIcon
|
||||||
35
src/renderer/src/components/IndicatorLight.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// src/renderer/src/components/IndicatorLight.tsx
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface IndicatorLightProps {
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Light = styled.div<{ color: string }>`
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: ${({ color }) => color};
|
||||||
|
box-shadow: 0 0 6px ${({ color }) => color};
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const IndicatorLight: React.FC<IndicatorLightProps> = ({ color }) => {
|
||||||
|
const actualColor = color === 'green' ? '#22c55e' : color
|
||||||
|
return <Light color={actualColor} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndicatorLight
|
||||||
83
src/renderer/src/components/ListItem/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface ListItemProps {
|
||||||
|
active?: boolean
|
||||||
|
icon?: ReactNode
|
||||||
|
title: string
|
||||||
|
subtitle?: string
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListItem = ({ active, icon, title, subtitle, onClick }: ListItemProps) => {
|
||||||
|
return (
|
||||||
|
<ListItemContainer className={active ? 'active' : ''} onClick={onClick}>
|
||||||
|
<ListItemContent>
|
||||||
|
{icon && <IconWrapper>{icon}</IconWrapper>}
|
||||||
|
<TextContainer>
|
||||||
|
<TitleText>{title}</TitleText>
|
||||||
|
{subtitle && <SubtitleText>{subtitle}</SubtitleText>}
|
||||||
|
</TextContainer>
|
||||||
|
</ListItemContent>
|
||||||
|
</ListItemContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListItemContainer = styled.div`
|
||||||
|
padding: 7px 12px;
|
||||||
|
border-radius: var(--list-item-border-radius);
|
||||||
|
font-size: 13px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
font-family: Ubuntu;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
border: 1px solid var(--color-border-soft);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ListItemContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const IconWrapper = styled.span`
|
||||||
|
margin-right: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const TextContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const TitleText = styled.div`
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SubtitleText = styled.div`
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--color-text-soft);
|
||||||
|
margin-top: 2px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
|
export default ListItem
|
||||||
@@ -5,6 +5,7 @@ import { useBridge } from '@renderer/hooks/useBridge'
|
|||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { setMinappShow } from '@renderer/store/runtime'
|
import { setMinappShow } from '@renderer/store/runtime'
|
||||||
import { MinAppType } from '@renderer/types'
|
import { MinAppType } from '@renderer/types'
|
||||||
|
import { delay } from '@renderer/utils'
|
||||||
import { Avatar, Drawer } from 'antd'
|
import { Avatar, Drawer } from 'antd'
|
||||||
import { WebviewTag } from 'electron'
|
import { WebviewTag } from 'electron'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
@@ -28,9 +29,10 @@ const PopupContainer: React.FC<Props> = ({ app, resolve }) => {
|
|||||||
|
|
||||||
const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://')
|
const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://')
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = async (_delay = 0.3) => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
setTimeout(() => resolve({}), 300)
|
await delay(_delay)
|
||||||
|
resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
MinApp.onClose = onClose
|
MinApp.onClose = onClose
|
||||||
@@ -58,7 +60,7 @@ const PopupContainer: React.FC<Props> = ({ app, resolve }) => {
|
|||||||
<ExportOutlined />
|
<ExportOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={onClose}>
|
<Button onClick={() => onClose()}>
|
||||||
<CloseOutlined />
|
<CloseOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonsGroup>
|
</ButtonsGroup>
|
||||||
@@ -99,7 +101,7 @@ const PopupContainer: React.FC<Props> = ({ app, resolve }) => {
|
|||||||
<Drawer
|
<Drawer
|
||||||
title={<Title />}
|
title={<Title />}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
onClose={onClose}
|
onClose={() => onClose()}
|
||||||
open={open}
|
open={open}
|
||||||
mask={true}
|
mask={true}
|
||||||
rootClassName="minapp-drawer"
|
rootClassName="minapp-drawer"
|
||||||
@@ -202,12 +204,22 @@ const EmptyView = styled.div`
|
|||||||
export default class MinApp {
|
export default class MinApp {
|
||||||
static topviewId = 0
|
static topviewId = 0
|
||||||
static onClose = () => {}
|
static onClose = () => {}
|
||||||
static close() {
|
static app: MinAppType | null = null
|
||||||
TopView.hide('MinApp')
|
|
||||||
store.dispatch(setMinappShow(false))
|
static async start(app: MinAppType) {
|
||||||
}
|
if (MinApp.app?.id === app.id) {
|
||||||
static start(app: MinAppType) {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MinApp.app) {
|
||||||
|
// @ts-ignore delay params
|
||||||
|
await MinApp.onClose(0)
|
||||||
|
await delay(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
MinApp.app = app
|
||||||
store.dispatch(setMinappShow(true))
|
store.dispatch(setMinappShow(true))
|
||||||
|
|
||||||
return new Promise<any>((resolve) => {
|
return new Promise<any>((resolve) => {
|
||||||
TopView.show(
|
TopView.show(
|
||||||
<PopupContainer
|
<PopupContainer
|
||||||
@@ -221,4 +233,10 @@ export default class MinApp {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static close() {
|
||||||
|
TopView.hide('MinApp')
|
||||||
|
store.dispatch(setMinappShow(false))
|
||||||
|
MinApp.app = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/renderer/src/components/ModelTags.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { isEmbeddingModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||||
|
import { Model } from '@renderer/types'
|
||||||
|
import { isFreeModel } from '@renderer/utils'
|
||||||
|
import { Tag } from 'antd'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
import VisionIcon from './Icons/VisionIcon'
|
||||||
|
import WebSearchIcon from './Icons/WebSearchIcon'
|
||||||
|
|
||||||
|
interface ModelTagsProps {
|
||||||
|
model: Model
|
||||||
|
showFree?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelTags: FC<ModelTagsProps> = ({ model, showFree = true }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isVisionModel(model) && <VisionIcon />}
|
||||||
|
{isWebSearchModel(model) && <WebSearchIcon />}
|
||||||
|
{showFree && isFreeModel(model) && (
|
||||||
|
<Tag style={{ marginLeft: 10 }} color="green">
|
||||||
|
{t('models.free')}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{isEmbeddingModel(model) && (
|
||||||
|
<Tag style={{ marginLeft: 10 }} color="orange">
|
||||||
|
{t('models.embedding')}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelTags
|
||||||
65
src/renderer/src/components/Popups/MinAppsPopover.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Center } from '@renderer/components/Layout'
|
||||||
|
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||||
|
import App from '@renderer/pages/apps/App'
|
||||||
|
import { Popover } from 'antd'
|
||||||
|
import { Empty } from 'antd'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import { FC, useState } from 'react'
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Scrollbar from '../Scrollbar'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const MinAppsPopover: FC<Props> = ({ children }) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const { minapps } = useMinapps()
|
||||||
|
|
||||||
|
useHotkeys('esc', () => {
|
||||||
|
setOpen(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<PopoverContent>
|
||||||
|
<AppsContainer>
|
||||||
|
{minapps.map((app) => (
|
||||||
|
<App key={app.id} app={app} onClick={handleClose} size={50} />
|
||||||
|
))}
|
||||||
|
{isEmpty(minapps) && (
|
||||||
|
<Center>
|
||||||
|
<Empty />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</AppsContainer>
|
||||||
|
</PopoverContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
content={content}
|
||||||
|
trigger="click"
|
||||||
|
placement="bottomRight"
|
||||||
|
styles={{ body: { padding: 25 } }}>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopoverContent = styled(Scrollbar)``
|
||||||
|
|
||||||
|
const AppsContainer = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, minmax(90px, 1fr));
|
||||||
|
gap: 18px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default MinAppsPopover
|
||||||
@@ -14,7 +14,7 @@ interface PromptPopupShowParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props extends PromptPopupShowParams {
|
interface Props extends PromptPopupShowParams {
|
||||||
resolve: (value: string) => void
|
resolve: (value: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PromptPopupContainer: React.FC<Props> = ({
|
const PromptPopupContainer: React.FC<Props> = ({
|
||||||
@@ -30,18 +30,21 @@ const PromptPopupContainer: React.FC<Props> = ({
|
|||||||
|
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
resolve(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
resolve(value)
|
resolve(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PromptPopup.hide = onCancel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={title} open={open} onOk={onOk} onCancel={handleCancel} afterClose={onClose} centered>
|
<Modal title={title} open={open} onOk={onOk} onCancel={onCancel} afterClose={onClose} centered>
|
||||||
<Box mb={8}>{message}</Box>
|
<Box mb={8}>{message}</Box>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
placeholder={inputPlaceholder}
|
placeholder={inputPlaceholder}
|
||||||
@@ -57,10 +60,12 @@ const PromptPopupContainer: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'PromptPopup'
|
||||||
|
|
||||||
export default class PromptPopup {
|
export default class PromptPopup {
|
||||||
static topviewId = 0
|
static topviewId = 0
|
||||||
static hide() {
|
static hide() {
|
||||||
TopView.hide('PromptPopup')
|
TopView.hide(TopViewKey)
|
||||||
}
|
}
|
||||||
static show(props: PromptPopupShowParams) {
|
static show(props: PromptPopupShowParams) {
|
||||||
return new Promise<string>((resolve) => {
|
return new Promise<string>((resolve) => {
|
||||||
@@ -69,7 +74,7 @@ export default class PromptPopup {
|
|||||||
{...props}
|
{...props}
|
||||||
resolve={(v) => {
|
resolve={(v) => {
|
||||||
resolve(v)
|
resolve(v)
|
||||||
this.hide()
|
TopView.hide(TopViewKey)
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
'PromptPopup'
|
'PromptPopup'
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { PushpinOutlined, SearchOutlined } from '@ant-design/icons'
|
import { PushpinOutlined, SearchOutlined } from '@ant-design/icons'
|
||||||
import VisionIcon from '@renderer/components/Icons/VisionIcon'
|
import VisionIcon from '@renderer/components/Icons/VisionIcon'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { getModelLogo, isVisionModel } from '@renderer/config/models'
|
import { getModelLogo, isEmbeddingModel, isVisionModel } from '@renderer/config/models'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useProviders } from '@renderer/hooks/useProvider'
|
import { useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { Avatar, Divider, Empty, Input, InputRef, Menu, MenuProps, Modal } from 'antd'
|
import { Avatar, Divider, Empty, Input, InputRef, Menu, MenuProps, Modal } from 'antd'
|
||||||
import { first, reverse, sortBy } from 'lodash'
|
import { first, sortBy } from 'lodash'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { HStack } from '../Layout'
|
import { HStack } from '../Layout'
|
||||||
|
import ModelTags from '../ModelTags'
|
||||||
import Scrollbar from '../Scrollbar'
|
import Scrollbar from '../Scrollbar'
|
||||||
|
|
||||||
type MenuItem = Required<MenuProps>['items'][number]
|
type MenuItem = Required<MenuProps>['items'][number]
|
||||||
@@ -47,7 +48,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
await db.settings.put({ id: 'pinned:models', value: validPinnedModels })
|
await db.settings.put({ id: 'pinned:models', value: validPinnedModels })
|
||||||
}
|
}
|
||||||
|
|
||||||
setPinnedModels(validPinnedModels)
|
setPinnedModels(sortBy(validPinnedModels, ['group', 'name']))
|
||||||
}
|
}
|
||||||
loadPinnedModels()
|
loadPinnedModels()
|
||||||
}, [providers])
|
}, [providers])
|
||||||
@@ -58,13 +59,14 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
: [...pinnedModels, modelId]
|
: [...pinnedModels, modelId]
|
||||||
|
|
||||||
await db.settings.put({ id: 'pinned:models', value: newPinnedModels })
|
await db.settings.put({ id: 'pinned:models', value: newPinnedModels })
|
||||||
setPinnedModels(newPinnedModels)
|
setPinnedModels(sortBy(newPinnedModels, ['group', 'name']))
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredItems: MenuItem[] = providers
|
const filteredItems: MenuItem[] = providers
|
||||||
.filter((p) => p.models && p.models.length > 0)
|
.filter((p) => p.models && p.models.length > 0)
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
const filteredModels = reverse(sortBy(p.models, 'name'))
|
const filteredModels = sortBy(p.models, ['group', 'name'])
|
||||||
|
.filter((m) => !isEmbeddingModel(m))
|
||||||
.filter((m) =>
|
.filter((m) =>
|
||||||
[m.name + m.provider + t('provider.' + p.id)].join('').toLowerCase().includes(searchText.toLowerCase())
|
[m.name + m.provider + t('provider.' + p.id)].join('').toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
@@ -73,7 +75,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
label: (
|
label: (
|
||||||
<ModelItem>
|
<ModelItem>
|
||||||
<span>
|
<span>
|
||||||
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
{m?.name} <ModelTags model={m} />
|
||||||
</span>
|
</span>
|
||||||
<PinIcon
|
<PinIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -113,7 +115,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
.flatMap((p) => p.models || [])
|
.flatMap((p) => p.models || [])
|
||||||
.filter((m) => pinnedModels.includes(getModelUniqId(m)))
|
.filter((m) => pinnedModels.includes(getModelUniqId(m)))
|
||||||
.map((m) => ({
|
.map((m) => ({
|
||||||
key: getModelUniqId(m),
|
key: getModelUniqId(m) + '_pinned',
|
||||||
label: (
|
label: (
|
||||||
<ModelItem>
|
<ModelItem>
|
||||||
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
||||||
@@ -141,7 +143,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
if (pinnedItems.length > 0) {
|
if (pinnedItems.length > 0) {
|
||||||
filteredItems.unshift({
|
filteredItems.unshift({
|
||||||
key: 'pinned',
|
key: 'pinned',
|
||||||
label: t('model.pinned'),
|
label: t('models.pinned'),
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: pinnedItems
|
children: pinnedItems
|
||||||
} as MenuItem)
|
} as MenuItem)
|
||||||
@@ -187,7 +189,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
</SearchIcon>
|
</SearchIcon>
|
||||||
}
|
}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder={t('model.search')}
|
placeholder={t('models.search')}
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
allowClear
|
allowClear
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TextAreaProps } from 'antd/lib/input'
|
|||||||
import { TextAreaRef } from 'antd/lib/input/TextArea'
|
import { TextAreaRef } from 'antd/lib/input/TextArea'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { TopView } from '../TopView'
|
import { TopView } from '../TopView'
|
||||||
|
|
||||||
@@ -11,13 +12,14 @@ interface ShowParams {
|
|||||||
text: string
|
text: string
|
||||||
textareaProps?: TextAreaProps
|
textareaProps?: TextAreaProps
|
||||||
modalProps?: ModalProps
|
modalProps?: ModalProps
|
||||||
|
children?: (props: { onOk?: () => void; onCancel?: () => void }) => React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props extends ShowParams {
|
interface Props extends ShowParams {
|
||||||
resolve: (data: any) => void
|
resolve: (data: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, resolve }) => {
|
const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, resolve, children }) => {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [textValue, setTextValue] = useState(text)
|
const [textValue, setTextValue] = useState(text)
|
||||||
@@ -49,6 +51,8 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
|||||||
setTimeout(resizeTextArea, 0)
|
setTimeout(resizeTextArea, 0)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
TextEditPopup.hide = onCancel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('common.edit')}
|
title={t('common.edit')}
|
||||||
@@ -66,19 +70,27 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
|||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
rows={2}
|
rows={2}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
spellCheck={false}
|
||||||
{...textareaProps}
|
{...textareaProps}
|
||||||
value={textValue}
|
value={textValue}
|
||||||
onInput={resizeTextArea}
|
onInput={resizeTextArea}
|
||||||
onChange={(e) => setTextValue(e.target.value)}
|
onChange={(e) => setTextValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<ChildrenContainer>{children && children({ onOk, onCancel })}</ChildrenContainer>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'TextEditPopup'
|
||||||
|
|
||||||
|
const ChildrenContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`
|
||||||
|
|
||||||
export default class TextEditPopup {
|
export default class TextEditPopup {
|
||||||
static topviewId = 0
|
static topviewId = 0
|
||||||
static hide() {
|
static hide() {
|
||||||
TopView.hide('TextEditPopup')
|
TopView.hide(TopViewKey)
|
||||||
}
|
}
|
||||||
static show(props: ShowParams) {
|
static show(props: ShowParams) {
|
||||||
return new Promise<any>((resolve) => {
|
return new Promise<any>((resolve) => {
|
||||||
@@ -87,10 +99,10 @@ export default class TextEditPopup {
|
|||||||
{...props}
|
{...props}
|
||||||
resolve={(v) => {
|
resolve={(v) => {
|
||||||
resolve(v)
|
resolve(v)
|
||||||
this.hide()
|
TopView.hide(TopViewKey)
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
'TextEditPopup'
|
TopViewKey
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
|||||||
const message = getUserMessage({
|
const message = getUserMessage({
|
||||||
assistant,
|
assistant,
|
||||||
topic: getDefaultTopic('default'),
|
topic: getDefaultTopic('default'),
|
||||||
type: 'text'
|
type: 'text',
|
||||||
|
content: text
|
||||||
})
|
})
|
||||||
|
|
||||||
const translatedText = await fetchTranslate({ message, assistant })
|
const translatedText = await fetchTranslate({ message, assistant })
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const NavbarCenterContainer = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 ${isMac ? '20px' : '15px'};
|
padding: 0 ${isMac ? '20px' : 0};
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import { FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
|
import { FileSearchOutlined, FolderOutlined, PictureOutlined, 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 { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||||
|
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import type { MenuProps } from 'antd'
|
||||||
import { Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
|
import { Dropdown } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import DragableList from '../DragableList'
|
||||||
|
import MinAppIcon from '../Icons/MinAppIcon'
|
||||||
import MinApp from '../MinApp'
|
import MinApp from '../MinApp'
|
||||||
import UserPopup from '../Popups/UserPopup'
|
import UserPopup from '../Popups/UserPopup'
|
||||||
|
|
||||||
@@ -19,25 +24,21 @@ const Sidebar: FC = () => {
|
|||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const avatar = useAvatar()
|
const avatar = useAvatar()
|
||||||
const { minappShow } = useRuntime()
|
const { minappShow } = useRuntime()
|
||||||
const { generating } = useRuntime()
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { windowStyle } = useSettings()
|
const { windowStyle, sidebarIcons } = useSettings()
|
||||||
const { theme, toggleTheme } = useTheme()
|
const { theme, toggleTheme } = useTheme()
|
||||||
|
const { pinned } = useMinapps()
|
||||||
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()
|
||||||
|
|
||||||
const macTransparentWindow = isMac && windowStyle === 'transparent'
|
const macTransparentWindow = isMac && windowStyle === 'transparent'
|
||||||
const sidebarBgColor = macTransparentWindow ? 'transparent' : 'var(--navbar-background)'
|
const sidebarBgColor = macTransparentWindow ? 'transparent' : 'var(--navbar-background)'
|
||||||
|
|
||||||
const to = (path: string) => {
|
const showPinnedApps = pinned.length > 0 && sidebarIcons.visible.includes('minapp')
|
||||||
if (generating) {
|
|
||||||
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
|
const to = async (path: string) => {
|
||||||
return
|
await modelGenerating()
|
||||||
}
|
|
||||||
navigate(path)
|
navigate(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,52 +50,19 @@ const Sidebar: FC = () => {
|
|||||||
zIndex: minappShow ? 10000 : 'initial'
|
zIndex: minappShow ? 10000 : 'initial'
|
||||||
}}>
|
}}>
|
||||||
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
|
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
|
||||||
<MainMenus>
|
<MainMenusContainer>
|
||||||
<Menus onClick={MinApp.onClose}>
|
<Menus onClick={MinApp.onClose}>
|
||||||
<Tooltip title={t('assistants.title')} mouseEnterDelay={0.8} placement="right">
|
<MainMenus />
|
||||||
<StyledLink onClick={() => to('/')}>
|
|
||||||
<Icon className={isRoute('/')}>
|
|
||||||
<i className="iconfont icon-chat" />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('agents.title')} mouseEnterDelay={0.8} placement="right">
|
|
||||||
<StyledLink onClick={() => to('/agents')}>
|
|
||||||
<Icon className={isRoutes('/agents')}>
|
|
||||||
<i className="iconfont icon-business-smart-assistant" />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('paintings.title')} mouseEnterDelay={0.8} placement="right">
|
|
||||||
<StyledLink onClick={() => to('/paintings')}>
|
|
||||||
<Icon className={isRoute('/paintings')}>
|
|
||||||
<PictureOutlined style={{ fontSize: 16 }} />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('translate.title')} mouseEnterDelay={0.8} placement="right">
|
|
||||||
<StyledLink onClick={() => to('/translate')}>
|
|
||||||
<Icon className={isRoute('/translate')}>
|
|
||||||
<TranslationOutlined />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('minapp.title')} mouseEnterDelay={0.8} placement="right">
|
|
||||||
<StyledLink onClick={() => to('/apps')}>
|
|
||||||
<Icon className={isRoute('/apps')}>
|
|
||||||
<i className="iconfont icon-appstore" />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('files.title')} mouseEnterDelay={0.8} placement="right">
|
|
||||||
<StyledLink onClick={() => to('/files')}>
|
|
||||||
<Icon className={isRoute('/files')}>
|
|
||||||
<FolderOutlined />
|
|
||||||
</Icon>
|
|
||||||
</StyledLink>
|
|
||||||
</Tooltip>
|
|
||||||
</Menus>
|
</Menus>
|
||||||
</MainMenus>
|
{showPinnedApps && (
|
||||||
|
<AppsContainer>
|
||||||
|
<Divider />
|
||||||
|
<Menus>
|
||||||
|
<PinnedApps />
|
||||||
|
</Menus>
|
||||||
|
</AppsContainer>
|
||||||
|
)}
|
||||||
|
</MainMenusContainer>
|
||||||
<Menus onClick={MinApp.onClose}>
|
<Menus onClick={MinApp.onClose}>
|
||||||
<Tooltip title={t('settings.theme.title')} mouseEnterDelay={0.8} placement="right">
|
<Tooltip title={t('settings.theme.title')} mouseEnterDelay={0.8} placement="right">
|
||||||
<Icon onClick={() => toggleTheme()}>
|
<Icon onClick={() => toggleTheme()}>
|
||||||
@@ -117,6 +85,82 @@ const Sidebar: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MainMenus: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { pathname } = useLocation()
|
||||||
|
const { sidebarIcons } = useSettings()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||||
|
const isRoutes = (path: string): string => (pathname.startsWith(path) ? 'active' : '')
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
assistants: <i className="iconfont icon-chat" />,
|
||||||
|
agents: <i className="iconfont icon-business-smart-assistant" />,
|
||||||
|
paintings: <PictureOutlined style={{ fontSize: 16 }} />,
|
||||||
|
translate: <TranslationOutlined />,
|
||||||
|
minapp: <i className="iconfont icon-appstore" />,
|
||||||
|
knowledge: <FileSearchOutlined />,
|
||||||
|
files: <FolderOutlined />
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathMap = {
|
||||||
|
assistants: '/',
|
||||||
|
agents: '/agents',
|
||||||
|
paintings: '/paintings',
|
||||||
|
translate: '/translate',
|
||||||
|
minapp: '/apps',
|
||||||
|
knowledge: '/knowledge',
|
||||||
|
files: '/files'
|
||||||
|
}
|
||||||
|
|
||||||
|
return sidebarIcons.visible.map((icon) => {
|
||||||
|
const path = pathMap[icon]
|
||||||
|
const isActive = path === '/' ? isRoute(path) : isRoutes(path)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip key={icon} title={t(`${icon}.title`)} mouseEnterDelay={0.8} placement="right">
|
||||||
|
<StyledLink onClick={() => navigate(path)}>
|
||||||
|
<Icon className={isActive}>{iconMap[icon]}</Icon>
|
||||||
|
</StyledLink>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const PinnedApps: FC = () => {
|
||||||
|
const { pinned, updatePinnedMinapps } = useMinapps()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragableList list={pinned} onUpdate={updatePinnedMinapps} listStyle={{ marginBottom: 5 }}>
|
||||||
|
{(app) => {
|
||||||
|
const menuItems: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'togglePin',
|
||||||
|
label: t('minapp.sidebar.remove.title'),
|
||||||
|
onClick: () => {
|
||||||
|
const newPinned = pinned.filter((item) => item.id !== app.id)
|
||||||
|
updatePinnedMinapps(newPinned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<Tooltip key={app.id} title={app.name} mouseEnterDelay={0.8} placement="right">
|
||||||
|
<StyledLink>
|
||||||
|
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']}>
|
||||||
|
<Icon onClick={() => MinApp.start(app)}>
|
||||||
|
<MinAppIcon size={20} app={app} style={{ borderRadius: 6 }} />
|
||||||
|
</Icon>
|
||||||
|
</Dropdown>
|
||||||
|
</StyledLink>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</DragableList>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -138,15 +182,18 @@ const AvatarImg = styled(Avatar)`
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`
|
`
|
||||||
const MainMenus = styled.div`
|
const MainMenusContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Menus = styled.div`
|
const Menus = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Icon = styled.div`
|
const Icon = styled.div`
|
||||||
@@ -156,7 +203,6 @@ const Icon = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-bottom: 5px;
|
|
||||||
-webkit-app-region: none;
|
-webkit-app-region: none;
|
||||||
border: 0.5px solid transparent;
|
border: 0.5px solid transparent;
|
||||||
.iconfont,
|
.iconfont,
|
||||||
@@ -194,4 +240,24 @@ const StyledLink = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const AppsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
-webkit-app-region: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
width: 50%;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-bottom: 0.5px solid var(--color-border);
|
||||||
|
`
|
||||||
|
|
||||||
export default Sidebar
|
export default Sidebar
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import AiAssistantAppLogo from '@renderer/assets/images/apps/360-ai.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 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 BoltAppLogo from '@renderer/assets/images/apps/bolt.svg'
|
||||||
@@ -8,14 +6,21 @@ import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png'
|
|||||||
import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp'
|
import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp'
|
||||||
import FeloAppLogo from '@renderer/assets/images/apps/felo.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 GensparkLogo from '@renderer/assets/images/apps/genspark.jpg'
|
||||||
|
import GithubCopilotLogo from '@renderer/assets/images/apps/github-copilot.webp'
|
||||||
|
import GrokAppLogo from '@renderer/assets/images/apps/grok.png'
|
||||||
|
import HikaLogo from '@renderer/assets/images/apps/hika.webp'
|
||||||
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'
|
||||||
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
|
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp'
|
||||||
|
import NamiAiSearchLogo from '@renderer/assets/images/apps/nm.webp'
|
||||||
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp'
|
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp'
|
||||||
import PoeAppLogo from '@renderer/assets/images/apps/poe.webp'
|
import PoeAppLogo from '@renderer/assets/images/apps/poe.webp'
|
||||||
import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png'
|
import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png'
|
||||||
|
import QwenlmAppLogo from '@renderer/assets/images/apps/qwenlm.webp'
|
||||||
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png'
|
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png'
|
||||||
import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.png'
|
import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.png'
|
||||||
|
import ThinkAnyLogo from '@renderer/assets/images/apps/thinkany.webp'
|
||||||
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png'
|
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png'
|
||||||
import WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg'
|
import WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg'
|
||||||
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png'
|
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png'
|
||||||
@@ -31,7 +36,7 @@ import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.p
|
|||||||
import MinApp from '@renderer/components/MinApp'
|
import MinApp from '@renderer/components/MinApp'
|
||||||
import { MinAppType } from '@renderer/types'
|
import { MinAppType } from '@renderer/types'
|
||||||
|
|
||||||
const _apps: MinAppType[] = [
|
export const DEFAULT_MIN_APPS: MinAppType[] = [
|
||||||
{
|
{
|
||||||
id: 'openai',
|
id: 'openai',
|
||||||
name: 'ChatGPT',
|
name: 'ChatGPT',
|
||||||
@@ -119,19 +124,6 @@ const _apps: MinAppType[] = [
|
|||||||
url: 'https://claude.ai/',
|
url: 'https://claude.ai/',
|
||||||
logo: ClaudeAppLogo
|
logo: ClaudeAppLogo
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: '360-ai-so',
|
|
||||||
name: '360AI搜索',
|
|
||||||
logo: AiSearchAppLogo,
|
|
||||||
url: 'https://so.360.com/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '360-ai-bot',
|
|
||||||
name: 'AI 助手',
|
|
||||||
logo: AiAssistantAppLogo,
|
|
||||||
url: 'https://bot.360.com/',
|
|
||||||
bodered: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'baidu-ai-chat',
|
id: 'baidu-ai-chat',
|
||||||
name: '文心一言',
|
name: '文心一言',
|
||||||
@@ -210,6 +202,12 @@ const _apps: MinAppType[] = [
|
|||||||
url: 'https://felo.ai/',
|
url: 'https://felo.ai/',
|
||||||
bodered: true
|
bodered: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'duckduckgo',
|
||||||
|
name: 'DuckDuckGo',
|
||||||
|
logo: DuckDuckGoAppLogo,
|
||||||
|
url: 'https://duck.ai'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'bolt',
|
id: 'bolt',
|
||||||
name: 'bolt',
|
name: 'bolt',
|
||||||
@@ -218,18 +216,54 @@ const _apps: MinAppType[] = [
|
|||||||
bodered: true
|
bodered: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'duckduckgo',
|
id: 'nm',
|
||||||
name: 'DuckDuckGo',
|
name: '纳米AI搜索',
|
||||||
logo: DuckDuckGoAppLogo,
|
logo: NamiAiSearchLogo,
|
||||||
url: 'https://duck.ai'
|
url: 'https://www.n.cn/',
|
||||||
|
bodered: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'thinkany',
|
||||||
|
name: 'ThinkAny',
|
||||||
|
logo: ThinkAnyLogo,
|
||||||
|
url: 'https://thinkany.ai/',
|
||||||
|
bodered: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hika',
|
||||||
|
name: 'Hika',
|
||||||
|
logo: HikaLogo,
|
||||||
|
url: 'https://hika.fyi/',
|
||||||
|
bodered: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'github-copilot',
|
||||||
|
name: 'GitHub Copilot',
|
||||||
|
logo: GithubCopilotLogo,
|
||||||
|
url: 'https://github.com/copilot'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'genspark',
|
||||||
|
name: 'Genspark',
|
||||||
|
logo: GensparkLogo,
|
||||||
|
url: 'https://www.genspark.ai/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grok',
|
||||||
|
name: 'Grok',
|
||||||
|
logo: GrokAppLogo,
|
||||||
|
url: 'https://x.com/i/grok',
|
||||||
|
bodered: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwenlm',
|
||||||
|
name: 'QwenLM',
|
||||||
|
logo: QwenlmAppLogo,
|
||||||
|
url: 'https://qwenlm.ai/'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export function getAllMinApps() {
|
|
||||||
return _apps as MinAppType[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startMinAppById(id: string) {
|
export function startMinAppById(id: string) {
|
||||||
const app = getAllMinApps().find((app) => app?.id === id)
|
const app = DEFAULT_MIN_APPS.find((app) => app?.id === id)
|
||||||
app && MinApp.start(app)
|
app && MinApp.start(app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import AisingaporeModelLogo from '@renderer/assets/images/models/aisingapore.png
|
|||||||
import AisingaporeModelLogoDark from '@renderer/assets/images/models/aisingapore_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 BigcodeModelLogo from '@renderer/assets/images/models/bigcode.webp'
|
||||||
import BigcodeModelLogoDark from '@renderer/assets/images/models/bigcode_dark.png'
|
import BigcodeModelLogoDark from '@renderer/assets/images/models/bigcode_dark.webp'
|
||||||
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'
|
||||||
@@ -121,9 +121,12 @@ import WenxinModelLogo from '@renderer/assets/images/models/wenxin.png'
|
|||||||
import WenxinModelLogoDark from '@renderer/assets/images/models/wenxin_dark.png'
|
import WenxinModelLogoDark from '@renderer/assets/images/models/wenxin_dark.png'
|
||||||
import YiModelLogo from '@renderer/assets/images/models/yi.png'
|
import YiModelLogo from '@renderer/assets/images/models/yi.png'
|
||||||
import YiModelLogoDark from '@renderer/assets/images/models/yi_dark.png'
|
import YiModelLogoDark from '@renderer/assets/images/models/yi_dark.png'
|
||||||
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
|
import { getWebSearchTools } from './tools'
|
||||||
|
|
||||||
const visionAllowedModels = [
|
const visionAllowedModels = [
|
||||||
'llava',
|
'llava',
|
||||||
'moondream',
|
'moondream',
|
||||||
@@ -136,7 +139,7 @@ const visionAllowedModels = [
|
|||||||
'qwen-vl',
|
'qwen-vl',
|
||||||
'qwen2-vl',
|
'qwen2-vl',
|
||||||
'internvl2',
|
'internvl2',
|
||||||
'grok',
|
'grok-vision-beta',
|
||||||
'pixtral',
|
'pixtral',
|
||||||
'gpt-4(?:-[\\w-]+)',
|
'gpt-4(?:-[\\w-]+)',
|
||||||
'gpt-4o(?:-[\\w-]+)?',
|
'gpt-4o(?:-[\\w-]+)?',
|
||||||
@@ -150,9 +153,9 @@ export const VISION_REGEX = new RegExp(
|
|||||||
'i'
|
'i'
|
||||||
)
|
)
|
||||||
|
|
||||||
const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
|
export 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
|
export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina)/i
|
||||||
const NOT_SUPPORTED_REGEX = /(?:^text-|embed|tts|rerank|whisper|speech|davinci|babbage|bge-|base|retrieval|uae-)/i
|
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
|
||||||
|
|
||||||
export function getModelLogo(modelId: string) {
|
export function getModelLogo(modelId: string) {
|
||||||
const isLight = true
|
const isLight = true
|
||||||
@@ -165,7 +168,7 @@ export function getModelLogo(modelId: string) {
|
|||||||
pixtral: isLight ? PixtralModelLogo : PixtralModelLogoDark,
|
pixtral: isLight ? PixtralModelLogo : PixtralModelLogoDark,
|
||||||
jina: isLight ? JinaModelLogo : JinaModelLogoDark,
|
jina: isLight ? JinaModelLogo : JinaModelLogoDark,
|
||||||
abab: isLight ? MinimaxModelLogo : MinimaxModelLogoDark,
|
abab: isLight ? MinimaxModelLogo : MinimaxModelLogoDark,
|
||||||
'o1-': isLight ? ChatGPTo1ModelLogo : ChatGPTo1ModelLogoDark,
|
o1: isLight ? ChatGPTo1ModelLogo : ChatGPTo1ModelLogoDark,
|
||||||
'gpt-3': isLight ? ChatGPT35ModelLogo : ChatGPT35ModelLogoDark,
|
'gpt-3': isLight ? ChatGPT35ModelLogo : ChatGPT35ModelLogoDark,
|
||||||
'gpt-4': isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
'gpt-4': isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
||||||
gpts: isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
gpts: isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
||||||
@@ -261,106 +264,70 @@ export function getModelLogo(modelId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SYSTEM_MODELS: Record<string, Model[]> = {
|
export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||||
|
aihubmix: [
|
||||||
|
{
|
||||||
|
id: 'gpt-4o',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'GPT-4o',
|
||||||
|
group: 'GPT-4o'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'claude-3-5-sonnet-latest',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'Claude 3.5 Sonnet',
|
||||||
|
group: 'Claude 3.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-2.0-flash-exp-search',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'Gemini 2.0 Flash Exp Search',
|
||||||
|
group: 'Gemini 2.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deepseek-chat',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'DeepSeek Chat',
|
||||||
|
group: 'DeepSeek Chat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'aihubmix-Llama-3-3-70B-Instruct',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'Llama-3.3-70b',
|
||||||
|
group: 'Llama 3.3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Qwen/QVQ-72B-Preview',
|
||||||
|
provider: 'aihubmix',
|
||||||
|
name: 'Qwen/QVQ-72B',
|
||||||
|
group: 'Qwen'
|
||||||
|
}
|
||||||
|
],
|
||||||
ollama: [],
|
ollama: [],
|
||||||
silicon: [
|
silicon: [
|
||||||
{
|
{
|
||||||
id: 'Qwen/Qwen2.5-72B-Instruct',
|
id: 'deepseek-ai/DeepSeek-V2.5',
|
||||||
|
name: 'deepseek-ai/DeepSeek-V2.5',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
name: 'Qwen2.5-72B-Instruct',
|
group: 'deepseek-ai'
|
||||||
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',
|
id: 'Qwen/Qwen2.5-7B-Instruct',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
name: 'Qwen2.5-7B-Instruct',
|
name: 'Qwen2.5-7B-Instruct',
|
||||||
group: 'Qwen2.5'
|
group: 'Qwen'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Qwen/Qwen2-7B-Instruct',
|
id: 'meta-llama/Llama-3.3-70B-Instruct',
|
||||||
|
name: 'meta-llama/Llama-3.3-70B-Instruct',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
name: 'Qwen2-7B-Instruct',
|
group: 'meta-llama'
|
||||||
group: 'Qwen2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Qwen/Qwen2-72B-Instruct',
|
|
||||||
provider: 'silicon',
|
|
||||||
name: 'Qwen2-72B-Instruct',
|
|
||||||
group: 'Qwen2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'THUDM/glm-4-9b-chat',
|
|
||||||
provider: 'silicon',
|
|
||||||
name: 'GLM-4-9B-Chat',
|
|
||||||
group: 'GLM'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek-ai/DeepSeek-V2-Chat',
|
|
||||||
provider: 'silicon',
|
|
||||||
name: 'DeepSeek-V2-Chat',
|
|
||||||
group: 'DeepSeek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek-ai/DeepSeek-Coder-V2-Instruct',
|
|
||||||
provider: 'silicon',
|
|
||||||
name: 'DeepSeek-Coder-V2-Instruct',
|
|
||||||
group: 'DeepSeek'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
openai: [
|
openai: [
|
||||||
{
|
{ id: 'gpt-4o', provider: 'openai', name: ' GPT-4o', group: 'GPT 4o' },
|
||||||
id: 'gpt-4o',
|
{ id: 'gpt-4o-mini', provider: 'openai', name: ' GPT-4o-mini', group: 'GPT 4o' },
|
||||||
provider: 'openai',
|
{ id: 'o1-mini', provider: 'openai', name: ' o1-mini', group: 'o1' },
|
||||||
name: ' GPT-4o',
|
{ id: 'o1-preview', provider: 'openai', name: ' o1-preview', group: 'o1' }
|
||||||
group: 'GPT 4o'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gpt-4o-mini',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' GPT-4o-mini',
|
|
||||||
group: 'GPT 4o'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chatgpt-4o-latest',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' GPT-4o-latest',
|
|
||||||
group: 'GPT 4o'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gpt-4-turbo',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' GPT-4 Turbo',
|
|
||||||
group: 'GPT 4'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gpt-4',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' GPT-4',
|
|
||||||
group: 'GPT 4'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'o1-mini',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' o1-mini',
|
|
||||||
group: 'o1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'o1-preview',
|
|
||||||
provider: 'openai',
|
|
||||||
name: ' o1-preview',
|
|
||||||
group: 'o1'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
'azure-openai': [
|
'azure-openai': [
|
||||||
{
|
{
|
||||||
@@ -384,10 +351,10 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
group: 'Gemini 1.5'
|
group: 'Gemini 1.5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gemini-1.5-pro-exp-0801',
|
id: 'gemini-1.5-pro',
|
||||||
|
name: 'Gemini 1.5 Pro',
|
||||||
provider: 'gemini',
|
provider: 'gemini',
|
||||||
name: 'Gemini 1.5 Pro Experimental 0801',
|
group: 'gemini-1.5'
|
||||||
group: 'Gemini 1.5'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
anthropic: [
|
anthropic: [
|
||||||
@@ -587,48 +554,28 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
yi: [
|
yi: [
|
||||||
{
|
{ id: 'yi-lightning', name: 'yi-lightning', provider: 'yi', group: 'yi-lightning', owned_by: '01.ai' },
|
||||||
id: 'yi-large',
|
{ id: 'yi-medium', name: 'yi-medium', provider: 'yi', group: 'yi-medium', owned_by: '01.ai' },
|
||||||
provider: 'yi',
|
{ id: 'yi-large', name: 'yi-large', provider: 'yi', group: 'yi-large', owned_by: '01.ai' },
|
||||||
name: 'Yi-Large',
|
{ id: 'yi-vision', name: 'yi-vision', provider: 'yi', group: 'yi-vision', owned_by: '01.ai' }
|
||||||
group: 'Yi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yi-large-turbo',
|
|
||||||
provider: 'yi',
|
|
||||||
name: 'Yi-Large-Turbo',
|
|
||||||
group: 'Yi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yi-large-rag',
|
|
||||||
provider: 'yi',
|
|
||||||
name: 'Yi-Large-Rag',
|
|
||||||
group: 'Yi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yi-medium',
|
|
||||||
provider: 'yi',
|
|
||||||
name: 'Yi-Medium',
|
|
||||||
group: 'Yi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yi-medium-200k',
|
|
||||||
provider: 'yi',
|
|
||||||
name: 'Yi-Medium-200k',
|
|
||||||
group: 'Yi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yi-spark',
|
|
||||||
provider: 'yi',
|
|
||||||
name: 'Yi-Spark',
|
|
||||||
group: 'Yi'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
zhipu: [
|
zhipu: [
|
||||||
{
|
{
|
||||||
id: 'glm-4',
|
id: 'glm-zero-preview',
|
||||||
provider: 'zhipu',
|
provider: 'zhipu',
|
||||||
name: 'GLM-4',
|
name: 'GLM-Zero-Preview',
|
||||||
|
group: 'GLM-Zero'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'glm-4-0520',
|
||||||
|
provider: 'zhipu',
|
||||||
|
name: 'GLM-4-0520',
|
||||||
|
group: 'GLM-4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'glm-4-long',
|
||||||
|
provider: 'zhipu',
|
||||||
|
name: 'GLM-4-Long',
|
||||||
group: 'GLM-4'
|
group: 'GLM-4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -655,6 +602,12 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
name: 'GLM-4-Flash',
|
name: 'GLM-4-Flash',
|
||||||
group: 'GLM-4'
|
group: 'GLM-4'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'glm-4-flashx',
|
||||||
|
provider: 'zhipu',
|
||||||
|
name: 'GLM-4-FlashX',
|
||||||
|
group: 'GLM-4'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'glm-4v',
|
id: 'glm-4v',
|
||||||
provider: 'zhipu',
|
provider: 'zhipu',
|
||||||
@@ -672,26 +625,21 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
provider: 'zhipu',
|
provider: 'zhipu',
|
||||||
name: 'GLM-4-AllTools',
|
name: 'GLM-4-AllTools',
|
||||||
group: 'GLM-4-AllTools'
|
group: 'GLM-4-AllTools'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'embedding-3',
|
||||||
|
provider: 'zhipu',
|
||||||
|
name: 'Embedding-3',
|
||||||
|
group: 'Embedding'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
moonshot: [
|
moonshot: [
|
||||||
{
|
{
|
||||||
id: 'moonshot-v1-8k',
|
id: 'moonshot-v1-auto',
|
||||||
|
name: 'moonshot-v1-auto',
|
||||||
provider: 'moonshot',
|
provider: 'moonshot',
|
||||||
name: 'Moonshot V1 8k',
|
group: 'moonshot-v1',
|
||||||
group: 'Moonshot V1'
|
owned_by: 'moonshot'
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'moonshot-v1-32k',
|
|
||||||
provider: 'moonshot',
|
|
||||||
name: 'Moonshot V1 32k',
|
|
||||||
group: 'Moonshot V1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'moonshot-v1-128k',
|
|
||||||
provider: 'moonshot',
|
|
||||||
name: 'Moonshot V1 128k',
|
|
||||||
group: 'Moonshot V1'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
baichuan: [
|
baichuan: [
|
||||||
@@ -715,24 +663,11 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
bailian: [
|
bailian: [
|
||||||
{
|
{ id: 'qwen-vl-plus', name: 'qwen-vl-plus', provider: 'dashscope', group: 'qwen-vl', owned_by: 'system' },
|
||||||
id: 'qwen-turbo',
|
{ id: 'qwen-coder-plus', name: 'qwen-coder-plus', provider: 'dashscope', group: 'qwen-coder', owned_by: 'system' },
|
||||||
provider: 'dashscope',
|
{ id: 'qwen-turbo', name: 'qwen-turbo', provider: 'dashscope', group: 'qwen-turbo', owned_by: 'system' },
|
||||||
name: 'Qwen Turbo',
|
{ id: 'qwen-plus', name: 'qwen-plus', provider: 'dashscope', group: 'qwen-plus', owned_by: 'system' },
|
||||||
group: 'Qwen'
|
{ id: 'qwen-max', name: 'qwen-max', provider: 'dashscope', group: 'qwen-max', owned_by: 'system' }
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'qwen-plus',
|
|
||||||
provider: 'dashscope',
|
|
||||||
name: 'Qwen Plus',
|
|
||||||
group: 'Qwen'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'qwen-max',
|
|
||||||
provider: 'dashscope',
|
|
||||||
name: 'Qwen Max',
|
|
||||||
group: 'Qwen'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
stepfun: [
|
stepfun: [
|
||||||
{
|
{
|
||||||
@@ -807,6 +742,12 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
provider: 'grok',
|
provider: 'grok',
|
||||||
name: 'Grok Beta',
|
name: 'Grok Beta',
|
||||||
group: 'Grok'
|
group: 'Grok'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grok-vision-beta',
|
||||||
|
provider: 'grok',
|
||||||
|
name: 'Grok Vision Beta',
|
||||||
|
group: 'Grok'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
mistral: [
|
mistral: [
|
||||||
@@ -823,19 +764,54 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
group: 'Mistral'
|
group: 'Mistral'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
jina: [],
|
jina: [
|
||||||
aihubmix: [
|
|
||||||
{
|
{
|
||||||
id: 'gpt-4o-mini',
|
id: 'jina-clip-v1',
|
||||||
provider: 'aihubmix',
|
provider: 'jina',
|
||||||
name: 'GPT-4o Mini',
|
name: 'jina-clip-v1',
|
||||||
group: 'GPT-4o'
|
group: 'Jina Clip'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'aihubmix-Llama-3-70B-Instruct',
|
id: 'jina-clip-v2',
|
||||||
provider: 'aihubmix',
|
provider: 'jina',
|
||||||
name: 'Llama 3 70B Instruct',
|
name: 'jina-clip-v2',
|
||||||
group: 'Llama3'
|
group: 'Jina Clip'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v2-base-en',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v2-base-en',
|
||||||
|
group: 'Jina Embeddings V2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v2-base-es',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v2-base-es',
|
||||||
|
group: 'Jina Embeddings V2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v2-base-de',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v2-base-de',
|
||||||
|
group: 'Jina Embeddings V2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v2-base-zh',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v2-base-zh',
|
||||||
|
group: 'Jina Embeddings V2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v2-base-code',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v2-base-code',
|
||||||
|
group: 'Jina Embeddings V2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jina-embeddings-v3',
|
||||||
|
provider: 'jina',
|
||||||
|
name: 'jina-embeddings-v3',
|
||||||
|
group: 'Jina Embeddings V3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
fireworks: [
|
fireworks: [
|
||||||
@@ -1035,18 +1011,91 @@ export const TEXT_TO_IMAGES_MODELS = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const TEXT_TO_IMAGES_MODELS_SUPPORT_IMAGE_ENHANCEMENT = [
|
||||||
|
'stabilityai/stable-diffusion-2-1',
|
||||||
|
'stabilityai/stable-diffusion-xl-base-1.0'
|
||||||
|
]
|
||||||
|
|
||||||
export function isTextToImageModel(model: Model): boolean {
|
export function isTextToImageModel(model: Model): boolean {
|
||||||
return TEXT_TO_IMAGE_REGEX.test(model.id)
|
return TEXT_TO_IMAGE_REGEX.test(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmbeddingModel(model: Model): boolean {
|
export function isEmbeddingModel(model: Model): boolean {
|
||||||
return EMBEDDING_REGEX.test(model.id)
|
if (!model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['anthropic'].includes(model?.provider)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMBEDDING_REGEX.test(model.id) || model.type?.includes('embedding') || false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVisionModel(model: Model): boolean {
|
export function isVisionModel(model: Model): boolean {
|
||||||
|
if (!model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSupportedModel(model: OpenAI.Models.Model): boolean {
|
export function isSupportedModel(model: OpenAI.Models.Model): boolean {
|
||||||
|
if (!model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return !NOT_SUPPORTED_REGEX.test(model.id)
|
return !NOT_SUPPORTED_REGEX.test(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isWebSearchModel(model: Model): boolean {
|
||||||
|
if (!model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = getProviderByModel(model)
|
||||||
|
|
||||||
|
if (provider.type === 'openai') {
|
||||||
|
if (model?.id?.includes('gemini-2.0-flash-exp')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'gemini' || provider?.type === 'gemini') {
|
||||||
|
return model?.id === 'gemini-2.0-flash-exp'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'hunyuan') {
|
||||||
|
return model?.id !== 'hunyuan-lite'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'aihubmix') {
|
||||||
|
return model?.id === 'gemini-2.0-flash-exp-search'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'zhipu') {
|
||||||
|
return model?.id?.startsWith('glm-4-')
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOpenAIWebSearchParams(model: Model): Record<string, any> {
|
||||||
|
if (isWebSearchModel(model)) {
|
||||||
|
const webSearchTools = getWebSearchTools(model)
|
||||||
|
|
||||||
|
if (model.provider === 'hunyuan') {
|
||||||
|
return { enable_enhancement: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tools: webSearchTools
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,7 +45,24 @@ export const AGENT_PROMPT = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const SUMMARIZE_PROMPT =
|
export const SUMMARIZE_PROMPT =
|
||||||
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号'
|
||||||
|
|
||||||
export const TRANSLATE_PROMPT =
|
export const TRANSLATE_PROMPT =
|
||||||
'Translate from input language to {{target_language}}, provide the translation result directly without any explanation, keep original format. If the target language is the same as the source language, do not translate. The text to be translated is as follows:\n\n{{text}}'
|
'You are a translation expert. Translate from input language to {{target_language}}, provide the translation result directly without any explanation and keep original format. Do not translate if the target language is the same as the source language.'
|
||||||
|
|
||||||
|
export const REFERENCE_PROMPT = `请根据参考资料回答问题,并使用脚注格式引用数据来源。请忽略无关的参考资料。
|
||||||
|
|
||||||
|
## 脚注格式:
|
||||||
|
|
||||||
|
1. **脚注标记**:在正文中使用 [^数字] 的形式标记脚注,例如 [^1]。
|
||||||
|
2. **脚注内容**:在文档末尾使用 [^数字]: 脚注内容 的形式定义脚注的具体内容
|
||||||
|
3. **脚注内容**:应该尽量简洁
|
||||||
|
|
||||||
|
## 我的问题是:
|
||||||
|
|
||||||
|
{question}
|
||||||
|
|
||||||
|
## 参考资料:
|
||||||
|
|
||||||
|
{references}
|
||||||
|
`
|
||||||
|
|||||||
@@ -355,11 +355,11 @@ export const PROVIDER_CONFIG = {
|
|||||||
},
|
},
|
||||||
aihubmix: {
|
aihubmix: {
|
||||||
api: {
|
api: {
|
||||||
url: 'https://aihubmix.com'
|
url: 'https://aihubmix.com?aff=SJyh'
|
||||||
},
|
},
|
||||||
websites: {
|
websites: {
|
||||||
official: 'https://aihubmix.com/',
|
official: 'https://aihubmix.com?aff=SJyh',
|
||||||
apiKey: 'https://aihubmix.com/token',
|
apiKey: 'https://aihubmix.com?aff=SJyh',
|
||||||
docs: 'https://doc.aihubmix.com/',
|
docs: 'https://doc.aihubmix.com/',
|
||||||
models: 'https://aihubmix.com/models'
|
models: 'https://aihubmix.com/models'
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/renderer/src/config/tools.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Model } from '@renderer/types'
|
||||||
|
import { ChatCompletionTool } from 'openai/resources'
|
||||||
|
|
||||||
|
export function getWebSearchTools(model: Model): ChatCompletionTool[] {
|
||||||
|
if (model?.provider === 'zhipu') {
|
||||||
|
if (model.id === 'glm-4-alltools') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'web_browser'
|
||||||
|
} as unknown as ChatCompletionTool
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'web_search',
|
||||||
|
web_search: {
|
||||||
|
enable: true,
|
||||||
|
search_result: true
|
||||||
|
}
|
||||||
|
} as unknown as ChatCompletionTool
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'googleSearch'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
|||||||
import { LanguageVarious } from '@renderer/types'
|
import { LanguageVarious } from '@renderer/types'
|
||||||
import { ConfigProvider, theme } from 'antd'
|
import { ConfigProvider, theme } from 'antd'
|
||||||
import enUS from 'antd/locale/en_US'
|
import enUS from 'antd/locale/en_US'
|
||||||
|
import jaJP from 'antd/locale/ja_JP'
|
||||||
import ruRU from 'antd/locale/ru_RU'
|
import ruRU from 'antd/locale/ru_RU'
|
||||||
import zhCN from 'antd/locale/zh_CN'
|
import zhCN from 'antd/locale/zh_CN'
|
||||||
import zhTW from 'antd/locale/zh_TW'
|
import zhTW from 'antd/locale/zh_TW'
|
||||||
@@ -31,6 +32,13 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
Menu: {
|
Menu: {
|
||||||
activeBarBorderWidth: 0,
|
activeBarBorderWidth: 0,
|
||||||
darkItemBg: 'transparent'
|
darkItemBg: 'transparent'
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
boxShadow: 'none',
|
||||||
|
boxShadowSecondary: 'none',
|
||||||
|
defaultShadow: 'none',
|
||||||
|
dangerShadow: 'none',
|
||||||
|
primaryShadow: 'none'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
@@ -52,6 +60,8 @@ function getAntdLocale(language: LanguageVarious) {
|
|||||||
return enUS
|
return enUS
|
||||||
case 'ru-RU':
|
case 'ru-RU':
|
||||||
return ruRU
|
return ruRU
|
||||||
|
case 'ja-JP':
|
||||||
|
return jaJP
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return zhCN
|
return zhCN
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
|
|||||||
const codeToHtml = async (code: string, language: string) => {
|
const codeToHtml = async (code: string, language: string) => {
|
||||||
if (!highlighter) return ''
|
if (!highlighter) return ''
|
||||||
|
|
||||||
|
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!highlighter.getLoadedLanguages().includes(language as BundledLanguage)) {
|
if (!highlighter.getLoadedLanguages().includes(language as BundledLanguage)) {
|
||||||
if (language in bundledLanguages) {
|
if (language in bundledLanguages || language === 'text') {
|
||||||
await highlighter.loadLanguage(language as BundledLanguage)
|
await highlighter.loadLanguage(language as BundledLanguage)
|
||||||
console.log(`Loaded language: ${language}`)
|
|
||||||
} else {
|
} else {
|
||||||
return `<pre style="padding: 10px"><code>${code}</code></pre>`
|
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
|
|||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Error highlighting code for language '${language}':`, error)
|
console.warn(`Error highlighting code for language '${language}':`, error)
|
||||||
return `<pre style="padding: 10px"><code>${code}</code></pre>`
|
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
import { FileType, Topic } from '@renderer/types'
|
import { FileType, KnowledgeItem, Topic } from '@renderer/types'
|
||||||
import { Dexie, type EntityTable } from 'dexie'
|
import { Dexie, type EntityTable } from 'dexie'
|
||||||
|
|
||||||
import { populateTopics } from './populate'
|
|
||||||
|
|
||||||
// Database declaration (move this to its own module also)
|
// Database declaration (move this to its own module also)
|
||||||
export const db = new Dexie('CherryStudio') as Dexie & {
|
export const db = new Dexie('CherryStudio') as Dexie & {
|
||||||
files: EntityTable<FileType, 'id'>
|
files: EntityTable<FileType, 'id'>
|
||||||
topics: EntityTable<Pick<Topic, 'id' | 'messages'>, 'id'>
|
topics: EntityTable<Pick<Topic, 'id' | 'messages'>, 'id'>
|
||||||
settings: EntityTable<{ id: string; value: any }, 'id'>
|
settings: EntityTable<{ id: string; value: any }, 'id'>
|
||||||
|
knowledge_notes: EntityTable<KnowledgeItem, 'id'>
|
||||||
}
|
}
|
||||||
|
|
||||||
db.version(1).stores({
|
db.version(1).stores({
|
||||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count'
|
files: 'id, name, origin_name, path, size, ext, type, created_at, count'
|
||||||
})
|
})
|
||||||
|
|
||||||
db.version(2)
|
db.version(2).stores({
|
||||||
.stores({
|
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
topics: '&id, messages',
|
||||||
topics: '&id, messages',
|
settings: '&id, value'
|
||||||
settings: '&id, value'
|
})
|
||||||
})
|
|
||||||
.upgrade(populateTopics)
|
|
||||||
|
|
||||||
db.on('populate', populateTopics)
|
db.version(3).stores({
|
||||||
|
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||||
|
topics: '&id, messages',
|
||||||
|
settings: '&id, value',
|
||||||
|
knowledge_notes: '&id, baseId, type, content, created_at, updated_at'
|
||||||
|
})
|
||||||
|
|
||||||
export default db
|
export default db
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import i18n from '@renderer/i18n'
|
|
||||||
import { Transaction } from 'dexie'
|
|
||||||
import localforage from 'localforage'
|
|
||||||
|
|
||||||
export async function populateTopics(trans: Transaction) {
|
|
||||||
const indexedKeys = await localforage.keys()
|
|
||||||
|
|
||||||
if (indexedKeys.length > 0) {
|
|
||||||
for (const key of indexedKeys) {
|
|
||||||
const value: any = await localforage.getItem(key)
|
|
||||||
if (key.startsWith('topic:')) {
|
|
||||||
await trans.db.table('topics').add({ id: value.id, messages: value.messages })
|
|
||||||
}
|
|
||||||
if (key === 'image://avatar') {
|
|
||||||
await trans.db.table('settings').add({ id: key, value: await localforage.getItem(key) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.modal.success({
|
|
||||||
title: i18n.t('message.upgrade.success.title'),
|
|
||||||
content: i18n.t('message.upgrade.success.content'),
|
|
||||||
okText: i18n.t('message.upgrade.success.button'),
|
|
||||||
centered: true,
|
|
||||||
onOk: () => window.api.reload()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
src/renderer/src/env.d.ts
vendored
@@ -19,5 +19,6 @@ declare global {
|
|||||||
modal: HookAPI
|
modal: HookAPI
|
||||||
keyv: KeyvStorage
|
keyv: KeyvStorage
|
||||||
mermaid: any
|
mermaid: any
|
||||||
|
store: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import { isMac, isWindows } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { isLocalAi } from '@renderer/config/env'
|
import { isLocalAi } from '@renderer/config/env'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setAvatar, setFilesPath } from '@renderer/store/runtime'
|
import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime'
|
||||||
import { updateShortcut } from '@renderer/store/shortcuts'
|
import { delay, runAsyncFunction } from '@renderer/utils'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
|
||||||
import { useLiveQuery } from 'dexie-react-hooks'
|
import { useLiveQuery } from 'dexie-react-hooks'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import { useDefaultModel } from './useAssistant'
|
import { useDefaultModel } from './useAssistant'
|
||||||
import { useRuntime } from './useRuntime'
|
import { useRuntime } from './useRuntime'
|
||||||
import { useSettings } from './useSettings'
|
import { useSettings } from './useSettings'
|
||||||
import { useShortcuts } from './useShortcuts'
|
import useUpdateHandler from './useUpdateHandler'
|
||||||
|
|
||||||
export function useAppInit() {
|
export function useAppInit() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { proxyUrl, language, windowStyle, manualUpdateCheck, proxyMode } = useSettings()
|
const { proxyUrl, language, windowStyle, manualUpdateCheck, proxyMode, customCss } = useSettings()
|
||||||
const { minappShow } = useRuntime()
|
const { minappShow } = useRuntime()
|
||||||
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
||||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||||
const { shortcuts } = useShortcuts()
|
|
||||||
|
useUpdateHandler()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||||
@@ -31,11 +31,12 @@ export function useAppInit() {
|
|||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const { isPackaged } = await window.api.getAppInfo()
|
const { isPackaged } = await window.api.getAppInfo()
|
||||||
if (isPackaged && !manualUpdateCheck) {
|
if (isPackaged && !manualUpdateCheck) {
|
||||||
setTimeout(window.api.checkForUpdate, 3000)
|
await delay(2)
|
||||||
|
const { updateInfo } = await window.api.checkForUpdate()
|
||||||
|
dispatch(setUpdateState({ info: updateInfo }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [dispatch, manualUpdateCheck])
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (proxyMode === 'system') {
|
if (proxyMode === 'system') {
|
||||||
@@ -74,13 +75,20 @@ export function useAppInit() {
|
|||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWindows) {
|
import('@renderer/queue/KnowledgeQueue')
|
||||||
shortcuts.forEach((shortcut) => {
|
}, [])
|
||||||
if (shortcut.shortcut[0] === 'Command') {
|
|
||||||
shortcut.shortcut[0] = 'Ctrl'
|
useEffect(() => {
|
||||||
dispatch(updateShortcut(shortcut))
|
const oldCustomCss = document.getElementById('user-defined-custom-css')
|
||||||
}
|
if (oldCustomCss) {
|
||||||
})
|
oldCustomCss.remove()
|
||||||
}
|
}
|
||||||
}, [dispatch, shortcuts])
|
|
||||||
|
if (customCss) {
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.id = 'user-defined-custom-css'
|
||||||
|
style.textContent = customCss
|
||||||
|
document.head.appendChild(style)
|
||||||
|
}
|
||||||
|
}, [customCss])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { db } from '@renderer/databases'
|
||||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
@@ -50,8 +51,20 @@ export function useAssistant(id: string) {
|
|||||||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||||||
},
|
},
|
||||||
moveTopic: (topic: Topic, toAssistant: Assistant) => {
|
moveTopic: (topic: Topic, toAssistant: Assistant) => {
|
||||||
dispatch(addTopic({ assistantId: toAssistant.id, topic: { ...topic } }))
|
dispatch(addTopic({ assistantId: toAssistant.id, topic: { ...topic, assistantId: toAssistant.id } }))
|
||||||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||||||
|
// update topic messages in database
|
||||||
|
db.topics
|
||||||
|
.where('id')
|
||||||
|
.equals(topic.id)
|
||||||
|
.modify((dbTopic) => {
|
||||||
|
if (dbTopic.messages) {
|
||||||
|
dbTopic.messages = dbTopic.messages.map((message) => ({
|
||||||
|
...message,
|
||||||
|
assistantId: toAssistant.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
|
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
|
||||||
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
|
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
|
||||||
|
|||||||
308
src/renderer/src/hooks/useKnowledge.ts
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
|
import { db } from '@renderer/databases/index'
|
||||||
|
import KnowledgeQueue from '@renderer/queue/KnowledgeQueue'
|
||||||
|
import FileManager from '@renderer/services/FileManager'
|
||||||
|
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
||||||
|
import { RootState } from '@renderer/store'
|
||||||
|
import {
|
||||||
|
addBase,
|
||||||
|
addFiles as addFilesAction,
|
||||||
|
addItem,
|
||||||
|
clearAllProcessing,
|
||||||
|
clearCompletedProcessing,
|
||||||
|
deleteBase,
|
||||||
|
removeItem as removeItemAction,
|
||||||
|
renameBase,
|
||||||
|
updateBase,
|
||||||
|
updateBases,
|
||||||
|
updateItem as updateItemAction,
|
||||||
|
updateItemProcessingStatus,
|
||||||
|
updateNotes
|
||||||
|
} from '@renderer/store/knowledge'
|
||||||
|
import { FileType, KnowledgeBase, ProcessingStatus } from '@renderer/types'
|
||||||
|
import { KnowledgeItem } from '@renderer/types'
|
||||||
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
export const useKnowledge = (baseId: string) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const base = useSelector((state: RootState) => state.knowledge.bases.find((b) => b.id === baseId))
|
||||||
|
|
||||||
|
// 重命名知识库
|
||||||
|
const renameKnowledgeBase = (name: string) => {
|
||||||
|
dispatch(renameBase({ baseId, name }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新知识库
|
||||||
|
const updateKnowledgeBase = (base: KnowledgeBase) => {
|
||||||
|
dispatch(updateBase(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量添加文件
|
||||||
|
const addFiles = (files: FileType[]) => {
|
||||||
|
const filesItems: KnowledgeItem[] = files.map((file) => ({
|
||||||
|
id: uuidv4(),
|
||||||
|
type: 'file' as const,
|
||||||
|
content: file,
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now(),
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
retryCount: 0
|
||||||
|
}))
|
||||||
|
dispatch(addFilesAction({ baseId, items: filesItems }))
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加URL
|
||||||
|
const addUrl = (url: string) => {
|
||||||
|
const newUrlItem: KnowledgeItem = {
|
||||||
|
id: uuidv4(),
|
||||||
|
type: 'url' as const,
|
||||||
|
content: url,
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now(),
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
retryCount: 0
|
||||||
|
}
|
||||||
|
dispatch(addItem({ baseId, item: newUrlItem }))
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加笔记
|
||||||
|
const addNote = async (content: string) => {
|
||||||
|
const noteId = uuidv4()
|
||||||
|
const note: KnowledgeItem = {
|
||||||
|
id: noteId,
|
||||||
|
type: 'note',
|
||||||
|
content,
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储完整笔记到数据库
|
||||||
|
await db.knowledge_notes.add(note)
|
||||||
|
|
||||||
|
// 在 store 中只存储引用
|
||||||
|
const noteRef: KnowledgeItem = {
|
||||||
|
id: noteId,
|
||||||
|
baseId,
|
||||||
|
type: 'note',
|
||||||
|
content: '', // store中不需要存储实际内容
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now(),
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
retryCount: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateNotes({ baseId, item: noteRef }))
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新笔记内容
|
||||||
|
const updateNoteContent = async (noteId: string, content: string) => {
|
||||||
|
const note = await db.knowledge_notes.get(noteId)
|
||||||
|
if (note) {
|
||||||
|
const updatedNote = {
|
||||||
|
...note,
|
||||||
|
content,
|
||||||
|
updated_at: Date.now()
|
||||||
|
}
|
||||||
|
await db.knowledge_notes.put(updatedNote)
|
||||||
|
dispatch(updateNotes({ baseId, item: updatedNote }))
|
||||||
|
}
|
||||||
|
const noteItem = base?.items.find((item) => item.id === noteId)
|
||||||
|
noteItem && refreshItem(noteItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取笔记内容
|
||||||
|
const getNoteContent = async (noteId: string) => {
|
||||||
|
return await db.knowledge_notes.get(noteId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateItem = (item: KnowledgeItem) => {
|
||||||
|
dispatch(updateItemAction({ baseId, item }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除项目
|
||||||
|
const removeItem = async (item: KnowledgeItem) => {
|
||||||
|
dispatch(removeItemAction({ baseId, item }))
|
||||||
|
if (base) {
|
||||||
|
if (item?.uniqueId) {
|
||||||
|
await window.api.knowledgeBase.remove({ uniqueId: item.uniqueId, base: getKnowledgeBaseParams(base) })
|
||||||
|
}
|
||||||
|
if (item.type === 'file' && typeof item.content === 'object') {
|
||||||
|
await FileManager.deleteFile(item.content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新项目
|
||||||
|
const refreshItem = async (item: KnowledgeItem) => {
|
||||||
|
const status = getProcessingStatus(item.id)
|
||||||
|
|
||||||
|
if (status === 'pending' || status === 'processing') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base && item.uniqueId) {
|
||||||
|
await window.api.knowledgeBase.remove({ uniqueId: item.uniqueId, base: getKnowledgeBaseParams(base) })
|
||||||
|
updateItem({
|
||||||
|
...item,
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
uniqueId: undefined
|
||||||
|
})
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新处理状态
|
||||||
|
const updateItemStatus = (itemId: string, status: ProcessingStatus, progress?: number, error?: string) => {
|
||||||
|
dispatch(
|
||||||
|
updateItemProcessingStatus({
|
||||||
|
baseId,
|
||||||
|
itemId,
|
||||||
|
status,
|
||||||
|
progress,
|
||||||
|
error
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取特定项目的处理状态
|
||||||
|
const getProcessingStatus = (itemId: string) => {
|
||||||
|
return base?.items.find((item) => item.id === itemId)?.processingStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取特定类型的所有处理项
|
||||||
|
const getProcessingItemsByType = (type: 'file' | 'url' | 'note') => {
|
||||||
|
return base?.items.filter((item) => item.type === type && item.processingStatus !== undefined) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除已完成的项目
|
||||||
|
const clearCompleted = () => {
|
||||||
|
dispatch(clearCompletedProcessing({ baseId }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除所有处理状态
|
||||||
|
const clearAll = () => {
|
||||||
|
dispatch(clearAllProcessing({ baseId }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 Sitemap
|
||||||
|
const addSitemap = (url: string) => {
|
||||||
|
const newSitemapItem: KnowledgeItem = {
|
||||||
|
id: uuidv4(),
|
||||||
|
type: 'sitemap' as const,
|
||||||
|
content: url,
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now(),
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
retryCount: 0
|
||||||
|
}
|
||||||
|
dispatch(addItem({ baseId, item: newSitemapItem }))
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add directory support
|
||||||
|
const addDirectory = (path: string) => {
|
||||||
|
const newDirectoryItem: KnowledgeItem = {
|
||||||
|
id: uuidv4(),
|
||||||
|
type: 'directory',
|
||||||
|
content: path,
|
||||||
|
created_at: Date.now(),
|
||||||
|
updated_at: Date.now(),
|
||||||
|
processingStatus: 'pending',
|
||||||
|
processingProgress: 0,
|
||||||
|
processingError: '',
|
||||||
|
retryCount: 0
|
||||||
|
}
|
||||||
|
dispatch(addItem({ baseId, item: newDirectoryItem }))
|
||||||
|
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileItems = base?.items.filter((item) => item.type === 'file') || []
|
||||||
|
const directoryItems = base?.items.filter((item) => item.type === 'directory') || []
|
||||||
|
const urlItems = base?.items.filter((item) => item.type === 'url') || []
|
||||||
|
const sitemapItems = base?.items.filter((item) => item.type === 'sitemap') || []
|
||||||
|
const [noteItems, setNoteItems] = useState<KnowledgeItem[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const notes = base?.items.filter((item) => item.type === 'note') || []
|
||||||
|
runAsyncFunction(async () => {
|
||||||
|
const newNoteItems = await Promise.all(
|
||||||
|
notes.map(async (item) => {
|
||||||
|
const note = await db.knowledge_notes.get(item.id)
|
||||||
|
return { ...item, content: note?.content || '' }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setNoteItems(newNoteItems.filter((note) => note !== undefined) as KnowledgeItem[])
|
||||||
|
})
|
||||||
|
}, [base?.items])
|
||||||
|
|
||||||
|
return {
|
||||||
|
base,
|
||||||
|
fileItems,
|
||||||
|
urlItems,
|
||||||
|
sitemapItems,
|
||||||
|
noteItems,
|
||||||
|
renameKnowledgeBase,
|
||||||
|
updateKnowledgeBase,
|
||||||
|
addFiles,
|
||||||
|
addUrl,
|
||||||
|
addSitemap,
|
||||||
|
addNote,
|
||||||
|
updateNoteContent,
|
||||||
|
getNoteContent,
|
||||||
|
updateItem,
|
||||||
|
updateItemStatus,
|
||||||
|
refreshItem,
|
||||||
|
getProcessingStatus,
|
||||||
|
getProcessingItemsByType,
|
||||||
|
clearCompleted,
|
||||||
|
clearAll,
|
||||||
|
removeItem,
|
||||||
|
directoryItems,
|
||||||
|
addDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useKnowledgeBases = () => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const bases = useSelector((state: RootState) => state.knowledge.bases)
|
||||||
|
|
||||||
|
const addKnowledgeBase = (base: KnowledgeBase) => {
|
||||||
|
dispatch(addBase(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
const renameKnowledgeBase = (baseId: string, name: string) => {
|
||||||
|
dispatch(renameBase({ baseId, name }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteKnowledgeBase = (baseId: string) => {
|
||||||
|
dispatch(deleteBase({ baseId }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateKnowledgeBases = (bases: KnowledgeBase[]) => {
|
||||||
|
dispatch(updateBases(bases))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bases,
|
||||||
|
addKnowledgeBase,
|
||||||
|
renameKnowledgeBase,
|
||||||
|
deleteKnowledgeBase,
|
||||||
|
updateKnowledgeBases
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,4 +37,32 @@ export const useMermaid = () => {
|
|||||||
|
|
||||||
setTimeout(renderMermaid, 100)
|
setTimeout(renderMermaid, 100)
|
||||||
}, [generating])
|
}, [generating])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleWheel = (e: WheelEvent) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
const mermaidElement = (e.target as HTMLElement).closest('.mermaid')
|
||||||
|
if (!mermaidElement) return
|
||||||
|
|
||||||
|
const svg = mermaidElement.querySelector('svg')
|
||||||
|
if (!svg) return
|
||||||
|
|
||||||
|
const currentScale = parseFloat(svg.style.transform?.match(/scale\((.*?)\)/)?.[1] || '1')
|
||||||
|
const delta = e.deltaY < 0 ? 0.1 : -0.1
|
||||||
|
const newScale = Math.max(0.1, Math.min(3, currentScale + delta))
|
||||||
|
|
||||||
|
const container = svg.parentElement
|
||||||
|
if (container) {
|
||||||
|
container.style.overflow = 'auto'
|
||||||
|
container.style.position = 'relative'
|
||||||
|
svg.style.transformOrigin = 'top left'
|
||||||
|
svg.style.transform = `scale(${newScale})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('wheel', handleWheel, { passive: false })
|
||||||
|
return () => document.removeEventListener('wheel', handleWheel)
|
||||||
|
}, [])
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/renderer/src/hooks/useMinapps.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { RootState, useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
|
import { setDisabledMinApps, setMinApps, setPinnedMinApps } from '@renderer/store/minapps'
|
||||||
|
import { MinAppType } from '@renderer/types'
|
||||||
|
|
||||||
|
export const useMinapps = () => {
|
||||||
|
const { enabled, disabled, pinned } = useAppSelector((state: RootState) => state.minapps)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return {
|
||||||
|
minapps: enabled,
|
||||||
|
disabled,
|
||||||
|
pinned,
|
||||||
|
updateMinapps: (minapps: MinAppType[]) => {
|
||||||
|
dispatch(setMinApps(minapps))
|
||||||
|
},
|
||||||
|
updateDisabledMinapps: (minapps: MinAppType[]) => {
|
||||||
|
dispatch(setDisabledMinApps(minapps))
|
||||||
|
},
|
||||||
|
updatePinnedMinapps: (minapps: MinAppType[]) => {
|
||||||
|
dispatch(setPinnedMinApps(minapps))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ export function usePaintings() {
|
|||||||
paintings,
|
paintings,
|
||||||
addPainting: () => {
|
addPainting: () => {
|
||||||
const newPainting: Painting = {
|
const newPainting: Painting = {
|
||||||
|
model: TEXT_TO_IMAGES_MODELS[0].id,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
urls: [],
|
urls: [],
|
||||||
files: [],
|
files: [],
|
||||||
@@ -24,7 +25,7 @@ export function usePaintings() {
|
|||||||
seed: generateRandomSeed(),
|
seed: generateRandomSeed(),
|
||||||
steps: 25,
|
steps: 25,
|
||||||
guidanceScale: 4.5,
|
guidanceScale: 4.5,
|
||||||
model: TEXT_TO_IMAGES_MODELS[0].id
|
promptEnhancement: true
|
||||||
}
|
}
|
||||||
dispatch(addPainting(newPainting))
|
dispatch(addPainting(newPainting))
|
||||||
return newPainting
|
return newPainting
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import { useAppSelector } from '@renderer/store'
|
import i18n from '@renderer/i18n'
|
||||||
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
|
|
||||||
export function useRuntime() {
|
export function useRuntime() {
|
||||||
return useAppSelector((state) => state.runtime)
|
return useAppSelector((state) => state.runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function modelGenerating() {
|
||||||
|
const generating = store.getState().runtime.generating
|
||||||
|
|
||||||
|
if (generating) {
|
||||||
|
window.message.warning({ content: i18n.t('message.switch.disabled'), key: 'model-generating' })
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
SendMessageShortcut,
|
SendMessageShortcut,
|
||||||
setSendMessageShortcut as _setSendMessageShortcut,
|
setSendMessageShortcut as _setSendMessageShortcut,
|
||||||
|
setSidebarIcons,
|
||||||
setTheme,
|
setTheme,
|
||||||
|
SettingsState,
|
||||||
setTopicPosition,
|
setTopicPosition,
|
||||||
setTray,
|
setTray,
|
||||||
setWindowStyle
|
setWindowStyle
|
||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
import { ThemeMode } from '@renderer/types'
|
import { SidebarIcon, ThemeMode } from '@renderer/types'
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
const settings = useAppSelector((state) => state.settings)
|
const settings = useAppSelector((state) => state.settings)
|
||||||
@@ -29,6 +31,15 @@ export function useSettings() {
|
|||||||
},
|
},
|
||||||
setTopicPosition(topicPosition: 'left' | 'right') {
|
setTopicPosition(topicPosition: 'left' | 'right') {
|
||||||
dispatch(setTopicPosition(topicPosition))
|
dispatch(setTopicPosition(topicPosition))
|
||||||
|
},
|
||||||
|
updateSidebarIcons(icons: { visible: SidebarIcon[]; disabled: SidebarIcon[] }) {
|
||||||
|
dispatch(setSidebarIcons(icons))
|
||||||
|
},
|
||||||
|
updateSidebarVisibleIcons(icons: SidebarIcon[]) {
|
||||||
|
dispatch(setSidebarIcons({ visible: icons }))
|
||||||
|
},
|
||||||
|
updateSidebarDisabledIcons(icons: SidebarIcon[]) {
|
||||||
|
dispatch(setSidebarIcons({ disabled: icons }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,3 +52,7 @@ export function useMessageStyle() {
|
|||||||
isBubbleStyle
|
isBubbleStyle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getStoreSetting = (key: keyof SettingsState) => {
|
||||||
|
return store.getState().settings[key]
|
||||||
|
}
|
||||||
|
|||||||