Compare commits
135 Commits
v0.8.19
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf61b1ce9 | ||
|
|
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 | ||
|
|
115f111071 | ||
|
|
a4d1bcffd9 | ||
|
|
f5d37a4e53 | ||
|
|
a9d4a0885c | ||
|
|
6596497c97 | ||
|
|
12d8f57dab | ||
|
|
7f2f3ad88a | ||
|
|
cd3c053f81 | ||
|
|
7dacd58821 | ||
|
|
744a6ac7cb | ||
|
|
2e9041c891 | ||
|
|
3717ff25bf | ||
|
|
494d52ac85 | ||
|
|
22d2ff1518 | ||
|
|
06ae4328ea | ||
|
|
8de1197557 | ||
|
|
09e86b35a5 | ||
|
|
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
|
||||
30
.github/workflows/release.yml
vendored
@@ -2,11 +2,6 @@ name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version (e.g. v1.2.3)'
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
@@ -34,18 +29,37 @@ jobs:
|
||||
- name: Install corepack
|
||||
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
|
||||
run: yarn install
|
||||
|
||||
- name: Build Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: yarn build:linux
|
||||
run: |
|
||||
yarn build:npm linux
|
||||
yarn build:linux
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Build Mac
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: yarn build:mac
|
||||
run: |
|
||||
yarn build:npm mac
|
||||
yarn build:mac
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
@@ -61,7 +75,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Replace spaces in filenames
|
||||
run: node scripts/replaceSpaces.js
|
||||
run: node scripts/replace-spaces.js
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
1
.gitignore
vendored
@@ -36,6 +36,7 @@ node_modules
|
||||
dist
|
||||
out
|
||||
build/icons
|
||||
stats.html
|
||||
|
||||
# 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
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
|
||||
index 00b67a48b7b5cf0029413fc84abd0c01630c3d14..5550b58495b468060f775ca86e4d849d82573ea5 100644
|
||||
index 30c91e66bf595a66c09eb3dbcbda7d58154865f5..b511ff24ea1891904c60174c6ed26ecdd4d5ac51 100644
|
||||
--- a/core.js
|
||||
+++ b/core.js
|
||||
@@ -156,7 +156,7 @@ class APIClient {
|
||||
@@ -12,7 +12,7 @@ index 00b67a48b7b5cf0029413fc84abd0c01630c3d14..5550b58495b468060f775ca86e4d849d
|
||||
};
|
||||
}
|
||||
diff --git a/core.mjs b/core.mjs
|
||||
index 8bc7a0ee10d61560d7113cf3f703355bb19f7ddd..5e4c8586ea6b13fe887a22af2de05eaa4700b5ec 100644
|
||||
index ac267bcfcff44b1f7c9bea5513bba94726a31795..dd5bd9f29609d3f0eea4bd5b225f302893df14ad 100644
|
||||
--- a/core.mjs
|
||||
+++ b/core.mjs
|
||||
@@ -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;
|
||||
|
||||
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
|
||||
@@ -1,6 +1,8 @@
|
||||
# provider: generic
|
||||
# url: http://127.0.0.1:8080
|
||||
# updaterCacheDirName: cherry-studio-updater
|
||||
provider: github
|
||||
repo: cherry-studio
|
||||
owner: kangfenmao
|
||||
# provider: github
|
||||
# repo: cherry-studio
|
||||
# owner: kangfenmao
|
||||
provider: generic
|
||||
url: https://cherrystudio.ocool.online
|
||||
|
||||
@@ -11,8 +11,25 @@ files:
|
||||
- '!src'
|
||||
- '!scripts'
|
||||
- '!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:
|
||||
- resources/**
|
||||
- '**/*.{node,dll,metal,exp,lib}'
|
||||
win:
|
||||
executableName: Cherry Studio
|
||||
nsis:
|
||||
@@ -22,14 +39,15 @@ nsis:
|
||||
createDesktopShortcut: always
|
||||
allowToChangeInstallationDirectory: true
|
||||
oneClick: false
|
||||
include: build/nsis-installer.nsh
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
notarize: false
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
target:
|
||||
- target: dmg
|
||||
arch:
|
||||
@@ -47,24 +65,17 @@ linux:
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
# - snap
|
||||
# - deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${productName}-${version}-${arch}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: github
|
||||
repo: cherry-studio
|
||||
owner: kangfenmao
|
||||
provider: generic
|
||||
url: https://cherrystudio.ocool.online
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
afterPack: scripts/after-pack.js
|
||||
afterSign: scripts/notarize.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
支持清除应用缓存
|
||||
支持编辑翻译模型提示词
|
||||
支持使用搜索内容快速创建助手
|
||||
支持编辑模型是否为视觉模型
|
||||
界面样式优化和错误修复
|
||||
增加 Genspark 小程序
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
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({
|
||||
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: {
|
||||
alias: {
|
||||
'@main': resolve('src/main'),
|
||||
'@types': resolve('src/renderer/src/types'),
|
||||
'@main': resolve('src/main')
|
||||
'@shared': resolve('packages/shared')
|
||||
}
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['@libsql/client']
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16,14 +42,15 @@ export default defineConfig({
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
plugins: [react(), ...visualizerPlugin('renderer')],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src')
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
'@shared': resolve('packages/shared')
|
||||
}
|
||||
},
|
||||
plugins: [react()],
|
||||
optimizeDeps: {
|
||||
exclude: ['chunk-KNVOMWSO.js']
|
||||
exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
64
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "0.8.19",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -11,9 +11,11 @@
|
||||
"local",
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"packages/database"
|
||||
]
|
||||
"installConfig": {
|
||||
"hoistingLimits": [
|
||||
"packages/database"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
@@ -23,22 +25,44 @@
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "electron-vite dev",
|
||||
"build:check": "yarn typecheck",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "dotenv npm run build && electron-builder --dir",
|
||||
"build:win": "dotenv npm run build && electron-builder --win --publish never",
|
||||
"build:mac": "dotenv electron-vite build && electron-builder --mac --publish never",
|
||||
"build:linux": "dotenv electron-vite build && electron-builder --linux --publish never",
|
||||
"build:win": "dotenv npm run build && electron-builder --win",
|
||||
"build:win:x64": "dotenv npm run build && electron-builder --win --x64",
|
||||
"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",
|
||||
"publish": "yarn release patch push",
|
||||
"pulish:artifacts": "cd packages/artifacts && npm publish && cd -",
|
||||
"generate:agents": "yarn workspace @cherry-studio/database agents",
|
||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build"
|
||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||
"analyze:main": "VISUALIZER_MAIN=true yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@electron/notarize": "^2.5.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": "^0.1.25",
|
||||
"@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",
|
||||
"apache-arrow": "^18.1.0",
|
||||
"docx": "^9.0.2",
|
||||
"electron-log": "^5.1.5",
|
||||
"electron-store": "^8.2.0",
|
||||
@@ -48,6 +72,7 @@
|
||||
"html2canvas": "^1.4.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"officeparser": "^4.1.1",
|
||||
"tokenx": "^0.4.1",
|
||||
"webdav": "4.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -55,7 +80,7 @@
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@google/generative-ai": "^0.16.0",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@hello-pangea/dnd": "^16.6.0",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
@@ -66,20 +91,21 @@
|
||||
"@types/node": "^18.19.9",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"antd": "^5.18.3",
|
||||
"axios": "^1.7.3",
|
||||
"antd": "^5.22.5",
|
||||
"axios": "^1.7.9",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"dexie": "^4.0.8",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"electron": "^28.3.3",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron": "31.7.6",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-icon-builder": "^2.0.1",
|
||||
"electron-vite": "^2.0.0",
|
||||
"electron-vite": "^2.3.0",
|
||||
"emittery": "^1.0.3",
|
||||
"emoji-picker-element": "^1.22.1",
|
||||
"eslint": "^8.56.0",
|
||||
@@ -87,16 +113,16 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.0.0",
|
||||
"gpt-tokens": "^1.3.10",
|
||||
"i18next": "^23.11.5",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash": "^4.17.21",
|
||||
"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",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router": "6",
|
||||
@@ -109,6 +135,7 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.2",
|
||||
"shiki": "^1.22.2",
|
||||
"styled-components": "^6.1.11",
|
||||
@@ -122,7 +149,8 @@
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
||||
115
packages/shared/config/constant.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
||||
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
|
||||
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||
export const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
'.mdx', // Markdown 文件
|
||||
'.html', // HTML 文件
|
||||
'.htm', // HTML 文件的另一种扩展名
|
||||
'.xml', // XML 文件
|
||||
'.json', // JSON 文件
|
||||
'.yaml', // YAML 文件
|
||||
'.yml', // YAML 文件的另一种扩展名
|
||||
'.csv', // 逗号分隔值文件
|
||||
'.tsv', // 制表符分隔值文件
|
||||
'.ini', // 配置文件
|
||||
'.log', // 日志文件
|
||||
'.rtf', // 富文本格式文件
|
||||
'.tex', // LaTeX 文件
|
||||
'.srt', // 字幕文件
|
||||
'.xhtml', // XHTML 文件
|
||||
'.nfo', // 信息文件(主要用于场景发布)
|
||||
'.conf', // 配置文件
|
||||
'.config', // 配置文件
|
||||
'.env', // 环境变量文件
|
||||
'.rst', // reStructuredText 文件
|
||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||
'.ts', // TypeScript 文件
|
||||
'.jsp', // JavaServer Pages 文件
|
||||
'.aspx', // ASP.NET 文件
|
||||
'.bat', // Windows 批处理文件
|
||||
'.sh', // Unix/Linux Shell 脚本文件
|
||||
'.py', // Python 脚本文件
|
||||
'.rb', // Ruby 脚本文件
|
||||
'.pl', // Perl 脚本文件
|
||||
'.sql', // SQL 脚本文件
|
||||
'.css', // Cascading Style Sheets 文件
|
||||
'.less', // Less CSS 预处理器文件
|
||||
'.scss', // Sass CSS 预处理器文件
|
||||
'.sass', // Sass 文件
|
||||
'.styl', // Stylus CSS 预处理器文件
|
||||
'.coffee', // CoffeeScript 文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.asm', // Assembly 语言文件
|
||||
'.go', // Go 语言文件
|
||||
'.scala', // Scala 语言文件
|
||||
'.swift', // Swift 语言文件
|
||||
'.kt', // Kotlin 语言文件
|
||||
'.rs', // Rust 语言文件
|
||||
'.lua', // Lua 语言文件
|
||||
'.groovy', // Groovy 语言文件
|
||||
'.dart', // Dart 语言文件
|
||||
'.hs', // Haskell 语言文件
|
||||
'.clj', // Clojure 语言文件
|
||||
'.cljs', // ClojureScript 语言文件
|
||||
'.elm', // Elm 语言文件
|
||||
'.erl', // Erlang 语言文件
|
||||
'.ex', // Elixir 语言文件
|
||||
'.exs', // Elixir 脚本文件
|
||||
'.pug', // Pug (formerly Jade) 模板文件
|
||||
'.haml', // Haml 模板文件
|
||||
'.slim', // Slim 模板文件
|
||||
'.tpl', // 模板文件(通用)
|
||||
'.ejs', // Embedded JavaScript 模板文件
|
||||
'.hbs', // Handlebars 模板文件
|
||||
'.mustache', // Mustache 模板文件
|
||||
'.jade', // Jade 模板文件 (已重命名为 Pug)
|
||||
'.twig', // Twig 模板文件
|
||||
'.blade', // Blade 模板文件 (Laravel)
|
||||
'.vue', // Vue.js 单文件组件
|
||||
'.jsx', // React JSX 文件
|
||||
'.tsx', // React TSX 文件
|
||||
'.graphql', // GraphQL 查询语言文件
|
||||
'.gql', // GraphQL 查询语言文件
|
||||
'.proto', // Protocol Buffers 文件
|
||||
'.thrift', // Thrift 文件
|
||||
'.toml', // TOML 配置文件
|
||||
'.edn', // Clojure 数据表示文件
|
||||
'.cake', // CakePHP 配置文件
|
||||
'.ctp', // CakePHP 视图文件
|
||||
'.cfm', // ColdFusion 标记语言文件
|
||||
'.cfc', // ColdFusion 组件文件
|
||||
'.m', // Objective-C 源文件
|
||||
'.mm', // Objective-C++ 源文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.groovy', // Gradle 构建文件
|
||||
'.kts', // Kotlin Script 文件
|
||||
'.java' // Java 代码文件
|
||||
]
|
||||
|
||||
export const ZOOM_SHORTCUTS = [
|
||||
{
|
||||
key: 'zoom_in',
|
||||
shortcut: ['CommandOrControl', '='],
|
||||
editable: false,
|
||||
enabled: true,
|
||||
system: true
|
||||
},
|
||||
{
|
||||
key: 'zoom_out',
|
||||
shortcut: ['CommandOrControl', '-'],
|
||||
editable: false,
|
||||
enabled: true,
|
||||
system: true
|
||||
},
|
||||
{
|
||||
key: 'zoom_reset',
|
||||
shortcut: ['CommandOrControl', '0'],
|
||||
editable: false,
|
||||
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
|
||||
}
|
||||
@@ -1,95 +1,3 @@
|
||||
export const isMac = process.platform === 'darwin'
|
||||
export const isWin = process.platform === 'win32'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
|
||||
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
||||
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
|
||||
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||
export const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
'.mdx', // Markdown 文件
|
||||
'.html', // HTML 文件
|
||||
'.htm', // HTML 文件的另一种扩展名
|
||||
'.xml', // XML 文件
|
||||
'.json', // JSON 文件
|
||||
'.yaml', // YAML 文件
|
||||
'.yml', // YAML 文件的另一种扩展名
|
||||
'.csv', // 逗号分隔值文件
|
||||
'.tsv', // 制表符分隔值文件
|
||||
'.ini', // 配置文件
|
||||
'.log', // 日志文件
|
||||
'.rtf', // 富文本格式文件
|
||||
'.tex', // LaTeX 文件
|
||||
'.srt', // 字幕文件
|
||||
'.xhtml', // XHTML 文件
|
||||
'.nfo', // 信息文件(主要用于场景发布)
|
||||
'.conf', // 配置文件
|
||||
'.config', // 配置文件
|
||||
'.env', // 环境变量文件
|
||||
'.rst', // reStructuredText 文件
|
||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||
'.ts', // TypeScript 文件
|
||||
'.jsp', // JavaServer Pages 文件
|
||||
'.aspx', // ASP.NET 文件
|
||||
'.bat', // Windows 批处理文件
|
||||
'.sh', // Unix/Linux Shell 脚本文件
|
||||
'.py', // Python 脚本文件
|
||||
'.rb', // Ruby 脚本文件
|
||||
'.pl', // Perl 脚本文件
|
||||
'.sql', // SQL 脚本文件
|
||||
'.css', // Cascading Style Sheets 文件
|
||||
'.less', // Less CSS 预处理器文件
|
||||
'.scss', // Sass CSS 预处理器文件
|
||||
'.sass', // Sass 文件
|
||||
'.styl', // Stylus CSS 预处理器文件
|
||||
'.coffee', // CoffeeScript 文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.asm', // Assembly 语言文件
|
||||
'.go', // Go 语言文件
|
||||
'.scala', // Scala 语言文件
|
||||
'.swift', // Swift 语言文件
|
||||
'.kt', // Kotlin 语言文件
|
||||
'.rs', // Rust 语言文件
|
||||
'.lua', // Lua 语言文件
|
||||
'.groovy', // Groovy 语言文件
|
||||
'.dart', // Dart 语言文件
|
||||
'.hs', // Haskell 语言文件
|
||||
'.clj', // Clojure 语言文件
|
||||
'.cljs', // ClojureScript 语言文件
|
||||
'.elm', // Elm 语言文件
|
||||
'.erl', // Erlang 语言文件
|
||||
'.ex', // Elixir 语言文件
|
||||
'.exs', // Elixir 脚本文件
|
||||
'.pug', // Pug (formerly Jade) 模板文件
|
||||
'.haml', // Haml 模板文件
|
||||
'.slim', // Slim 模板文件
|
||||
'.tpl', // 模板文件(通用)
|
||||
'.ejs', // Embedded JavaScript 模板文件
|
||||
'.hbs', // Handlebars 模板文件
|
||||
'.mustache', // Mustache 模板文件
|
||||
'.jade', // Jade 模板文件 (已重命名为 Pug)
|
||||
'.twig', // Twig 模板文件
|
||||
'.blade', // Blade 模板文件 (Laravel)
|
||||
'.vue', // Vue.js 单文件组件
|
||||
'.jsx', // React JSX 文件
|
||||
'.tsx', // React TSX 文件
|
||||
'.graphql', // GraphQL 查询语言文件
|
||||
'.gql', // GraphQL 查询语言文件
|
||||
'.proto', // Protocol Buffers 文件
|
||||
'.thrift', // Thrift 文件
|
||||
'.toml', // TOML 配置文件
|
||||
'.edn', // Clojure 数据表示文件
|
||||
'.cake', // CakePHP 配置文件
|
||||
'.ctp', // CakePHP 视图文件
|
||||
'.cfm', // ColdFusion 标记语言文件
|
||||
'.cfc', // ColdFusion 组件文件
|
||||
'.m', // Objective-C 源文件
|
||||
'.mm', // Objective-C++ 源文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.groovy', // Gradle 构建文件
|
||||
'.kts', // Kotlin Script 文件
|
||||
'.java' // Java 代码文件
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app, BrowserWindow } from 'electron'
|
||||
import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'
|
||||
|
||||
import { registerIpc } from './ipc'
|
||||
import { registerZoomShortcut } from './services/ShortcutService'
|
||||
import { registerShortcuts } from './services/ShortcutService'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { updateUserDataPath } from './utils/upgrade'
|
||||
@@ -35,7 +35,7 @@ if (!app.requestSingleInstanceLock()) {
|
||||
}
|
||||
})
|
||||
|
||||
registerZoomShortcut(mainWindow)
|
||||
registerShortcuts(mainWindow)
|
||||
|
||||
registerIpc(mainWindow, app)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import vm from 'node:vm'
|
||||
|
||||
import { ThemeMode } from '@types'
|
||||
import { Shortcut, ThemeMode } from '@types'
|
||||
import axios from 'axios'
|
||||
import { BrowserWindow, ipcMain, ProxyConfig, session, shell } from 'electron'
|
||||
import log from 'electron-log'
|
||||
|
||||
@@ -11,6 +13,8 @@ import BackupManager from './services/BackupManager'
|
||||
import { configManager } from './services/ConfigManager'
|
||||
import { ExportService } from './services/ExportService'
|
||||
import FileStorage from './services/FileStorage'
|
||||
import KnowledgeService from './services/KnowledgeService'
|
||||
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { compress, decompress } from './utils/zip'
|
||||
|
||||
@@ -32,8 +36,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
ipcMain.handle('app:proxy', async (_, proxy: string) => {
|
||||
const sessions = [session.defaultSession, session.fromPartition('persist:webview')]
|
||||
const proxyConfig: ProxyConfig =
|
||||
proxy === 'system' ? { mode: 'system' } : proxy ? { proxyRules: proxy } : { mode: 'direct' }
|
||||
const proxyConfig: ProxyConfig = proxy === 'system' ? { mode: 'system' } : proxy ? { proxyRules: proxy } : {}
|
||||
await Promise.all(sessions.map((session) => session.setProxy(proxyConfig)))
|
||||
})
|
||||
|
||||
@@ -81,9 +84,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
// check for update
|
||||
ipcMain.handle('app:check-for-update', async () => {
|
||||
const update = await autoUpdater.checkForUpdates()
|
||||
return {
|
||||
currentVersion: autoUpdater.currentVersion,
|
||||
update: await autoUpdater.checkForUpdates()
|
||||
updateInfo: update?.updateInfo
|
||||
}
|
||||
})
|
||||
|
||||
@@ -99,6 +103,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
// file
|
||||
ipcMain.handle('file:open', fileManager.open)
|
||||
ipcMain.handle('file:openPath', fileManager.openPath)
|
||||
ipcMain.handle('file:save', fileManager.save)
|
||||
ipcMain.handle('file:select', fileManager.selectFile)
|
||||
ipcMain.handle('file:upload', fileManager.uploadFile)
|
||||
@@ -133,4 +138,28 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle('open:path', async (_, path: string) => {
|
||||
await shell.openPath(path)
|
||||
})
|
||||
|
||||
// shortcuts
|
||||
ipcMain.handle('shortcuts:update', (_, shortcuts: Shortcut[]) => {
|
||||
configManager.setShortcuts(shortcuts)
|
||||
// Refresh shortcuts registration
|
||||
if (mainWindow) {
|
||||
unregisterAllShortcuts()
|
||||
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)
|
||||
|
||||
// vm
|
||||
ipcMain.handle('run-js', (_, code: string) => {
|
||||
const context = vm.createContext(Object.assign({ fetch: fetch, URL: URL, axios: axios }, global))
|
||||
return vm.runInContext(code, context)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ import { app, BrowserWindow, dialog } from 'electron'
|
||||
import logger from 'electron-log'
|
||||
import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater'
|
||||
|
||||
import icon from '../../../build/icon.png?asset'
|
||||
|
||||
export default class AppUpdater {
|
||||
autoUpdater: _AppUpdater = autoUpdater
|
||||
|
||||
constructor(mainWindow: BrowserWindow) {
|
||||
logger.transports.file.level = 'debug'
|
||||
logger.transports.file.level = 'info'
|
||||
|
||||
autoUpdater.logger = logger
|
||||
autoUpdater.forceDevUpdateConfig = true
|
||||
autoUpdater.forceDevUpdateConfig = !app.isPackaged
|
||||
autoUpdater.autoDownload = true
|
||||
|
||||
// 检测下载错误
|
||||
@@ -18,7 +21,7 @@ export default class AppUpdater {
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
||||
autoUpdater.logger?.info('检测到新版本,开始自动下载')
|
||||
logger.info('检测到新版本', releaseInfo)
|
||||
mainWindow.webContents.send('update-available', releaseInfo)
|
||||
})
|
||||
|
||||
@@ -29,18 +32,22 @@ export default class AppUpdater {
|
||||
|
||||
// 更新下载进度
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
logger.info('下载进度', progress)
|
||||
mainWindow.webContents.send('download-progress', progress)
|
||||
})
|
||||
|
||||
// 当需要更新的内容下载完成后
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
logger.info('下载完成,询问用户是否更新')
|
||||
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||||
mainWindow.webContents.send('update-downloaded')
|
||||
|
||||
logger.info('下载完成,询问用户是否更新', releaseInfo)
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: '安装更新',
|
||||
message: '更新已下载完成,是否立即安装?',
|
||||
icon,
|
||||
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
||||
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
||||
buttons: ['稍后安装', '立即安装'],
|
||||
defaultId: 1,
|
||||
cancelId: 0
|
||||
@@ -49,10 +56,29 @@ export default class AppUpdater {
|
||||
if (response === 1) {
|
||||
app.isQuitting = true
|
||||
setImmediate(() => autoUpdater.quitAndInstall())
|
||||
} else {
|
||||
mainWindow.webContents.send('update-downloaded-cancelled')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LanguageVarious, ThemeMode } from '@types'
|
||||
import { ZOOM_SHORTCUTS } from '@shared/config/constant'
|
||||
import { LanguageVarious, Shortcut, ThemeMode } from '@types'
|
||||
import { app } from 'electron'
|
||||
import Store from 'electron-store'
|
||||
|
||||
@@ -70,6 +71,18 @@ export class ConfigManager {
|
||||
subscribers.forEach((subscriber) => subscriber(newValue))
|
||||
}
|
||||
}
|
||||
|
||||
getShortcuts() {
|
||||
return this.store.get('shortcuts', ZOOM_SHORTCUTS) as Shortcut[] | []
|
||||
}
|
||||
|
||||
setShortcuts(shortcuts: Shortcut[]) {
|
||||
this.store.set(
|
||||
'shortcuts',
|
||||
shortcuts.filter((shortcut) => shortcut.system)
|
||||
)
|
||||
this.notifySubscribers('shortcuts', shortcuts)
|
||||
}
|
||||
}
|
||||
|
||||
export const configManager = new ConfigManager()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { documentExts, imageExts } from '@main/constant'
|
||||
import { getFileType } from '@main/utils/file'
|
||||
import { documentExts, imageExts } from '@shared/config/constant'
|
||||
import { FileType } from '@types'
|
||||
import * as crypto from 'crypto'
|
||||
import {
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue,
|
||||
SaveDialogOptions,
|
||||
SaveDialogReturnValue
|
||||
SaveDialogReturnValue,
|
||||
shell
|
||||
} from 'electron'
|
||||
import logger from 'electron-log'
|
||||
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 (
|
||||
_: Electron.IpcMainInvokeEvent,
|
||||
fileName: string,
|
||||
|
||||
154
src/main/services/KnowledgeService.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
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: 15
|
||||
})
|
||||
: new OpenAiEmbeddings({
|
||||
model,
|
||||
apiKey,
|
||||
configuration: { baseURL },
|
||||
dimensions,
|
||||
batchSize: 15
|
||||
})
|
||||
)
|
||||
.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')) {
|
||||
return await ragApplication.addLoader(new WebLoader({ urlOrContent: content }), forceReload)
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type === 'sitemap') {
|
||||
const content = item.content as string
|
||||
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', '.mdx'].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,70 +1,131 @@
|
||||
import { Shortcut } from '@types'
|
||||
import { BrowserWindow, globalShortcut } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
|
||||
import { configManager } from './ConfigManager'
|
||||
|
||||
export function registerZoomShortcut(mainWindow: BrowserWindow) {
|
||||
// 初始化缩放值
|
||||
const initialZoom = configManager.getZoomFactor()
|
||||
mainWindow.webContents.setZoomFactor(initialZoom)
|
||||
let showAppAccelerator: string | null = null
|
||||
|
||||
const handleZoom = (delta: number) => {
|
||||
if (mainWindow) {
|
||||
const currentZoom = mainWindow.webContents.getZoomFactor()
|
||||
const newZoom = currentZoom + delta
|
||||
if (newZoom >= 0.1 && newZoom <= 5.0) {
|
||||
mainWindow.webContents.setZoomFactor(newZoom)
|
||||
configManager.setZoomFactor(newZoom)
|
||||
function getShortcutHandler(shortcut: Shortcut) {
|
||||
switch (shortcut.key) {
|
||||
case 'zoom_in':
|
||||
return (window: BrowserWindow) => handleZoom(0.1)(window)
|
||||
case 'zoom_out':
|
||||
return (window: BrowserWindow) => handleZoom(-0.1)(window)
|
||||
case 'zoom_reset':
|
||||
return (window: BrowserWindow) => {
|
||||
window.webContents.setZoomFactor(1)
|
||||
configManager.setZoomFactor(1)
|
||||
}
|
||||
case 'show_app':
|
||||
return (window: BrowserWindow) => {
|
||||
if (window.isVisible()) {
|
||||
window.hide()
|
||||
} else {
|
||||
window.show()
|
||||
window.focus()
|
||||
}
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function formatShortcutKey(shortcut: string[]): string {
|
||||
return shortcut.join('+')
|
||||
}
|
||||
|
||||
function handleZoom(delta: number) {
|
||||
return (window: BrowserWindow) => {
|
||||
const currentZoom = window.webContents.getZoomFactor()
|
||||
const newZoom = currentZoom + delta
|
||||
if (newZoom >= 0.1 && newZoom <= 5.0) {
|
||||
window.webContents.setZoomFactor(newZoom)
|
||||
configManager.setZoomFactor(newZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const registerShortcuts = () => {
|
||||
// 放大快捷键
|
||||
globalShortcut.register('CommandOrControl+=', () => handleZoom(0.1))
|
||||
globalShortcut.register('CommandOrControl+numadd', () => handleZoom(0.1))
|
||||
export function registerShortcuts(window: BrowserWindow) {
|
||||
window.webContents.setZoomFactor(configManager.getZoomFactor())
|
||||
|
||||
// 缩小快捷键
|
||||
globalShortcut.register('CommandOrControl+-', () => handleZoom(-0.1))
|
||||
globalShortcut.register('CommandOrControl+numsub', () => handleZoom(-0.1))
|
||||
const register = () => {
|
||||
if (window.isDestroyed()) return
|
||||
|
||||
// 重置快捷键
|
||||
globalShortcut.register('CommandOrControl+0', () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.setZoomFactor(1)
|
||||
configManager.setZoomFactor(1)
|
||||
const shortcuts = configManager.getShortcuts()
|
||||
if (!shortcuts) return
|
||||
|
||||
shortcuts.forEach((shortcut) => {
|
||||
try {
|
||||
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) {
|
||||
globalShortcut.register(accelerator, () => handler(window))
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`[ShortcutService] Failed to register shortcut ${shortcut.key}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const unregisterShortcuts = () => {
|
||||
globalShortcut.unregister('CommandOrControl+=')
|
||||
globalShortcut.unregister('CommandOrControl+numadd')
|
||||
globalShortcut.unregister('CommandOrControl+-')
|
||||
globalShortcut.unregister('CommandOrControl+numsub')
|
||||
globalShortcut.unregister('CommandOrControl+0')
|
||||
const unregister = () => {
|
||||
if (window.isDestroyed()) return
|
||||
|
||||
try {
|
||||
globalShortcut.unregisterAll()
|
||||
|
||||
if (showAppAccelerator) {
|
||||
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
|
||||
handler && globalShortcut.register(showAppAccelerator, () => handler(window))
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error('[ShortcutService] Failed to unregister shortcuts')
|
||||
}
|
||||
}
|
||||
|
||||
// Add check for window destruction
|
||||
if (mainWindow.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
window.on('focus', () => register())
|
||||
window.on('blur', () => unregister())
|
||||
|
||||
// When window gains focus, register shortcuts
|
||||
mainWindow.on('focus', () => {
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
registerShortcuts()
|
||||
}
|
||||
})
|
||||
|
||||
// When window loses focus, unregister shortcuts
|
||||
mainWindow.on('blur', () => {
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
unregisterShortcuts()
|
||||
}
|
||||
})
|
||||
|
||||
// Initial registration (if window is already focused)
|
||||
if (!mainWindow.isDestroyed() && mainWindow.isFocused()) {
|
||||
registerShortcuts()
|
||||
if (!window.isDestroyed() && window.isFocused()) {
|
||||
register()
|
||||
}
|
||||
}
|
||||
|
||||
export function unregisterAllShortcuts() {
|
||||
try {
|
||||
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 { isLinux, isWin } from '@main/constant'
|
||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import { join } from 'path'
|
||||
import path, { join } from 'path'
|
||||
|
||||
import icon from '../../../build/icon.png?asset'
|
||||
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
||||
@@ -32,6 +33,7 @@ export class WindowService {
|
||||
|
||||
const theme = configManager.getTheme()
|
||||
const isMac = process.platform === 'darwin'
|
||||
const isLinux = process.platform === 'linux'
|
||||
|
||||
this.mainWindow = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
@@ -45,7 +47,7 @@ export class WindowService {
|
||||
transparent: isMac,
|
||||
vibrancy: 'under-window',
|
||||
visualEffectState: 'active',
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarStyle: isLinux ? 'default' : 'hidden',
|
||||
titleBarOverlay: theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight,
|
||||
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
|
||||
trafficLightPosition: { x: 8, y: 12 },
|
||||
@@ -123,12 +125,26 @@ export class WindowService {
|
||||
|
||||
private setupWebContentsHandlers(mainWindow: BrowserWindow) {
|
||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||
if (url.includes('localhost:5173')) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
|
||||
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' }
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@main/constant'
|
||||
|
||||
import { FileTypes } from '../../renderer/src/types'
|
||||
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@shared/config/constant'
|
||||
import { FileTypes } from '@types'
|
||||
|
||||
export function getFileType(ext: string): FileTypes {
|
||||
ext = ext.toLowerCase()
|
||||
|
||||
@@ -14,3 +14,11 @@ export function getDataPath() {
|
||||
}
|
||||
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 JaJP from '../../renderer/src/i18n/locales/ja-jp.json'
|
||||
import RuRu from '../../renderer/src/i18n/locales/ru-ru.json'
|
||||
import ZhCn from '../../renderer/src/i18n/locales/zh-cn.json'
|
||||
import ZhTw from '../../renderer/src/i18n/locales/zh-tw.json'
|
||||
@@ -7,6 +8,7 @@ const locales = {
|
||||
'en-US': EnUs,
|
||||
'zh-CN': ZhCn,
|
||||
'zh-TW': ZhTw,
|
||||
'ja-JP': JaJP,
|
||||
'ru-RU': RuRu
|
||||
}
|
||||
|
||||
|
||||
29
src/preload/index.d.ts
vendored
@@ -1,8 +1,10 @@
|
||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
import { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces'
|
||||
import { FileType } 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 { UpdateInfo } from 'electron-updater'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
declare global {
|
||||
@@ -10,7 +12,7 @@ declare global {
|
||||
electron: ElectronAPI
|
||||
api: {
|
||||
getAppInfo: () => Promise<AppInfo>
|
||||
checkForUpdate: () => void
|
||||
checkForUpdate: () => Promise<{ currentVersion: string; updateInfo: UpdateInfo | null }>
|
||||
openWebsite: (url: string) => void
|
||||
setProxy: (proxy: string | undefined) => void
|
||||
setLanguage: (theme: LanguageVarious) => void
|
||||
@@ -40,6 +42,7 @@ declare global {
|
||||
create: (fileName: string) => Promise<string>
|
||||
write: (filePath: string, data: Uint8Array | string) => Promise<void>
|
||||
open: (options?: OpenDialogOptions) => Promise<{ fileName: string; filePath: string; content: Buffer } | null>
|
||||
openPath: (path: string) => Promise<void>
|
||||
save: (
|
||||
path: string,
|
||||
content: string | NodeJS.ArrayBufferView,
|
||||
@@ -54,6 +57,28 @@ declare global {
|
||||
toWord: (markdown: string, fileName: string) => Promise<void>
|
||||
}
|
||||
openPath: (path: string) => Promise<void>
|
||||
shortcuts: {
|
||||
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[]>
|
||||
}
|
||||
vm: {
|
||||
run: (code: string) => Promise<any>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
import { WebDavConfig } from '@types'
|
||||
import { KnowledgeBaseParams, KnowledgeItem, Shortcut, WebDavConfig } from '@types'
|
||||
import { contextBridge, ipcRenderer, OpenDialogOptions } from 'electron'
|
||||
|
||||
// Custom APIs for renderer
|
||||
@@ -36,6 +36,7 @@ const api = {
|
||||
create: (fileName: string) => ipcRenderer.invoke('file:create', fileName),
|
||||
write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke('file:write', filePath, data),
|
||||
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 }) =>
|
||||
ipcRenderer.invoke('file:save', path, content, options),
|
||||
selectFolder: () => ipcRenderer.invoke('file:selectFolder'),
|
||||
@@ -47,7 +48,32 @@ const api = {
|
||||
export: {
|
||||
toWord: (markdown: string, fileName: string) => ipcRenderer.invoke('export:word', markdown, fileName)
|
||||
},
|
||||
openPath: (path: string) => ipcRenderer.invoke('open:path', path)
|
||||
openPath: (path: string) => ipcRenderer.invoke('open:path', path),
|
||||
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 })
|
||||
},
|
||||
vm: {
|
||||
run: (code: string) => ipcRenderer.invoke('run-js', code)
|
||||
}
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
@@ -14,6 +14,7 @@ import AgentsPage from './pages/agents/AgentsPage'
|
||||
import AppsPage from './pages/apps/AppsPage'
|
||||
import FilesPage from './pages/files/FilesPage'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import KnowledgePage from './pages/knowledge/KnowledgePage'
|
||||
import PaintingsPage from './pages/paintings/PaintingsPage'
|
||||
import SettingsPage from './pages/settings/SettingsPage'
|
||||
import TranslatePage from './pages/translate/TranslatePage'
|
||||
@@ -30,10 +31,11 @@ function App(): JSX.Element {
|
||||
<Sidebar />
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/paintings" element={<PaintingsPage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 4563475 */
|
||||
src: url('iconfont.woff2?t=1725606177995') format('woff2');
|
||||
font-family: 'iconfont'; /* Project id 4753420 */
|
||||
src: url('iconfont.woff2?t=1733224456443') format('woff2');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -11,6 +11,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-at1:before {
|
||||
content: '\e7df';
|
||||
}
|
||||
|
||||
.icon-at:before {
|
||||
content: '\e630';
|
||||
}
|
||||
|
||||
.icon-a-darkmode:before {
|
||||
content: '\e6cd';
|
||||
}
|
||||
@@ -27,10 +35,6 @@
|
||||
content: '\e942';
|
||||
}
|
||||
|
||||
.icon-grid-row-2copy:before {
|
||||
content: '\e681';
|
||||
}
|
||||
|
||||
.icon-inbox:before {
|
||||
content: '\e869';
|
||||
}
|
||||
@@ -71,10 +75,6 @@
|
||||
content: '\e944';
|
||||
}
|
||||
|
||||
.icon-a-addchat:before {
|
||||
content: '\e658';
|
||||
}
|
||||
|
||||
.icon-appstore:before {
|
||||
content: '\e792';
|
||||
}
|
||||
|
||||
|
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/nm.webp
Normal file
|
After Width: | Height: | Size: 3.2 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: 44 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 |
@@ -42,6 +42,7 @@
|
||||
--color-active: rgba(55, 55, 55, 1);
|
||||
--color-frame-border: #333;
|
||||
--color-group-background: var(--color-background-soft);
|
||||
--color-reference-background: #0b0e12;
|
||||
|
||||
--navbar-background-mac: rgba(30, 30, 30, 0.6);
|
||||
--navbar-background: rgba(30, 30, 30);
|
||||
@@ -99,6 +100,7 @@ body[theme-mode='light'] {
|
||||
--color-active: var(--color-white-soft);
|
||||
--color-frame-border: #ddd;
|
||||
--color-group-background: var(--color-white);
|
||||
--color-reference-background: #f1f7ff;
|
||||
|
||||
--navbar-background-mac: rgba(255, 255, 255, 0.6);
|
||||
--navbar-background: rgba(255, 255, 255);
|
||||
@@ -169,19 +171,10 @@ body,
|
||||
border-top: 0.5px solid var(--color-border);
|
||||
}
|
||||
|
||||
body[os='mac'] {
|
||||
#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);
|
||||
}
|
||||
#content-container {
|
||||
border-top-left-radius: 12px;
|
||||
border-left: 0.5px solid var(--color-border);
|
||||
box-shadow: -2px 0px 20px -4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.loader {
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: initial;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
@@ -225,11 +229,24 @@
|
||||
|
||||
.footnotes {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
background-color: var(--color-reference-background);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
|
||||
h4 {
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 1em;
|
||||
margin: 0;
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
|
||||
@@ -14,8 +14,8 @@ body[theme-mode='light'] {
|
||||
|
||||
/* 全局初始化滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-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
|
||||
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
|
||||
84
src/renderer/src/components/ListItem/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
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) => {
|
||||
const borderRadius = subtitle ? '10px' : '16px'
|
||||
return (
|
||||
<ListItemContainer className={active ? 'active' : ''} onClick={onClick} style={{ borderRadius }}>
|
||||
<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: 16px;
|
||||
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
|
||||
65
src/renderer/src/components/Popups/AppStorePopover.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { getAllMinApps } from '@renderer/config/minapps'
|
||||
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 AppStorePopover: FC<Props> = ({ children }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const apps = getAllMinApps()
|
||||
|
||||
useHotkeys('esc', () => {
|
||||
setOpen(false)
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const content = (
|
||||
<PopoverContent>
|
||||
<AppsContainer>
|
||||
{apps.map((app) => (
|
||||
<App key={app.id} app={app} onClick={handleClose} size={50} />
|
||||
))}
|
||||
{isEmpty(apps) && (
|
||||
<Center>
|
||||
<Empty />
|
||||
</Center>
|
||||
)}
|
||||
</AppsContainer>
|
||||
</PopoverContent>
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
content={content}
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
overlayInnerStyle={{ padding: 25 }}>
|
||||
{children}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
const PopoverContent = styled(Scrollbar)``
|
||||
|
||||
const AppsContainer = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(90px, 1fr));
|
||||
gap: 25px;
|
||||
`
|
||||
|
||||
export default AppStorePopover
|
||||
@@ -14,7 +14,7 @@ interface PromptPopupShowParams {
|
||||
}
|
||||
|
||||
interface Props extends PromptPopupShowParams {
|
||||
resolve: (value: string) => void
|
||||
resolve: (value: any) => void
|
||||
}
|
||||
|
||||
const PromptPopupContainer: React.FC<Props> = ({
|
||||
@@ -30,18 +30,21 @@ const PromptPopupContainer: React.FC<Props> = ({
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
resolve(value)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
resolve(value)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
PromptPopup.hide = onCancel
|
||||
|
||||
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>
|
||||
<Input.TextArea
|
||||
placeholder={inputPlaceholder}
|
||||
@@ -57,10 +60,12 @@ const PromptPopupContainer: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'PromptPopup'
|
||||
|
||||
export default class PromptPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('PromptPopup')
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
static show(props: PromptPopupShowParams) {
|
||||
return new Promise<string>((resolve) => {
|
||||
@@ -69,7 +74,7 @@ export default class PromptPopup {
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
'PromptPopup'
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { PushpinOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import VisionIcon from '@renderer/components/Icons/VisionIcon'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { getModelLogo, isVisionModel } from '@renderer/config/models'
|
||||
import { getModelLogo, isEmbeddingModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { Model } from '@renderer/types'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import WebSearchIcon from '../Icons/WebSearchIcon'
|
||||
import { HStack } from '../Layout'
|
||||
import Scrollbar from '../Scrollbar'
|
||||
|
||||
@@ -47,7 +48,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
await db.settings.put({ id: 'pinned:models', value: validPinnedModels })
|
||||
}
|
||||
|
||||
setPinnedModels(validPinnedModels)
|
||||
setPinnedModels(sortBy(validPinnedModels, ['group', 'name']))
|
||||
}
|
||||
loadPinnedModels()
|
||||
}, [providers])
|
||||
@@ -58,13 +59,14 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
: [...pinnedModels, modelId]
|
||||
|
||||
await db.settings.put({ id: 'pinned:models', value: newPinnedModels })
|
||||
setPinnedModels(newPinnedModels)
|
||||
setPinnedModels(sortBy(newPinnedModels, ['group', 'name']))
|
||||
}
|
||||
|
||||
const filteredItems: MenuItem[] = providers
|
||||
.filter((p) => p.models && p.models.length > 0)
|
||||
.map((p) => {
|
||||
const filteredModels = reverse(sortBy(p.models, 'name'))
|
||||
const filteredModels = sortBy(p.models, ['group', 'name'])
|
||||
.filter((m) => !isEmbeddingModel(m))
|
||||
.filter((m) =>
|
||||
[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: (
|
||||
<ModelItem>
|
||||
<span>
|
||||
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
||||
{m?.name} {isVisionModel(m) && <VisionIcon />} {isWebSearchModel(m) && <WebSearchIcon />}
|
||||
</span>
|
||||
<PinIcon
|
||||
onClick={(e) => {
|
||||
@@ -113,7 +115,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
.flatMap((p) => p.models || [])
|
||||
.filter((m) => pinnedModels.includes(getModelUniqId(m)))
|
||||
.map((m) => ({
|
||||
key: getModelUniqId(m),
|
||||
key: getModelUniqId(m) + '_pinned',
|
||||
label: (
|
||||
<ModelItem>
|
||||
{m?.name} {isVisionModel(m) && <VisionIcon />}
|
||||
@@ -141,7 +143,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
if (pinnedItems.length > 0) {
|
||||
filteredItems.unshift({
|
||||
key: 'pinned',
|
||||
label: t('model.pinned'),
|
||||
label: t('models.pinned'),
|
||||
type: 'group',
|
||||
children: pinnedItems
|
||||
} as MenuItem)
|
||||
@@ -187,7 +189,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
</SearchIcon>
|
||||
}
|
||||
ref={inputRef}
|
||||
placeholder={t('model.search')}
|
||||
placeholder={t('models.search')}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
|
||||
@@ -36,7 +36,8 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="ant-move-down">
|
||||
transitionName="ant-move-down"
|
||||
centered>
|
||||
<Box mb={8}>Name</Box>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TextAreaProps } from 'antd/lib/input'
|
||||
import { TextAreaRef } from 'antd/lib/input/TextArea'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { TopView } from '../TopView'
|
||||
|
||||
@@ -11,13 +12,14 @@ interface ShowParams {
|
||||
text: string
|
||||
textareaProps?: TextAreaProps
|
||||
modalProps?: ModalProps
|
||||
children?: (props: { onOk?: () => void; onCancel?: () => void }) => React.ReactNode
|
||||
}
|
||||
|
||||
interface Props extends ShowParams {
|
||||
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 { t } = useTranslation()
|
||||
const [textValue, setTextValue] = useState(text)
|
||||
@@ -49,6 +51,8 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
||||
setTimeout(resizeTextArea, 0)
|
||||
}, [])
|
||||
|
||||
TextEditPopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('common.edit')}
|
||||
@@ -71,14 +75,21 @@ const PopupContainer: React.FC<Props> = ({ text, textareaProps, modalProps, reso
|
||||
onInput={resizeTextArea}
|
||||
onChange={(e) => setTextValue(e.target.value)}
|
||||
/>
|
||||
<ChildrenContainer>{children && children({ onOk, onCancel })}</ChildrenContainer>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'TextEditPopup'
|
||||
|
||||
const ChildrenContainer = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
export default class TextEditPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('TextEditPopup')
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
@@ -87,10 +98,10 @@ export default class TextEditPopup {
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
'TextEditPopup'
|
||||
TopViewKey
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import { TranslationOutlined } from '@ant-design/icons'
|
||||
import { LoadingOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||
import { getDefaultTopic, getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import { getUserMessage } from '@renderer/services/MessagesService'
|
||||
import { Button } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
text?: string
|
||||
onTranslated: (translatedText: string) => void
|
||||
disabled?: boolean
|
||||
style?: React.CSSProperties
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style }) => {
|
||||
const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoading }) => {
|
||||
const { t } = useTranslation()
|
||||
const { translateModel } = useDefaultModel()
|
||||
const [isTranslating, setIsTranslating] = useState(false)
|
||||
|
||||
const translateConfirm = () => {
|
||||
return window?.modal?.confirm({
|
||||
title: t('translate.confirm.title'),
|
||||
content: t('translate.confirm.content'),
|
||||
centered: true
|
||||
})
|
||||
}
|
||||
|
||||
const handleTranslate = async () => {
|
||||
if (!text?.trim()) return
|
||||
|
||||
if (!(await translateConfirm())) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!translateModel) {
|
||||
window.message.error({
|
||||
content: t('translate.error.not_configured'),
|
||||
@@ -39,7 +53,8 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style }) =>
|
||||
const message = getUserMessage({
|
||||
assistant,
|
||||
topic: getDefaultTopic('default'),
|
||||
type: 'text'
|
||||
type: 'text',
|
||||
content: text
|
||||
})
|
||||
|
||||
const translatedText = await fetchTranslate({ message, assistant })
|
||||
@@ -55,16 +70,53 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style }) =>
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsTranslating(isLoading ?? false)
|
||||
}, [isLoading])
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={<TranslationOutlined style={{ fontSize: 14 }} />}
|
||||
onClick={handleTranslate}
|
||||
disabled={disabled || isTranslating}
|
||||
loading={isTranslating}
|
||||
style={style}
|
||||
size="small"
|
||||
/>
|
||||
<Tooltip placement="top" title={t('chat.input.translate')} arrow>
|
||||
<ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text">
|
||||
{isTranslating ? <LoadingOutlined spin /> : <TranslationOutlined />}
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const ToolbarButton = styled(Button)`
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
font-size: 17px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
color: var(--color-icon);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
&.anticon,
|
||||
&.iconfont {
|
||||
transition: all 0.3s ease;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
.anticon,
|
||||
.iconfont {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-primary) !important;
|
||||
.anticon,
|
||||
.iconfont {
|
||||
color: var(--color-white-soft);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default TranslateButton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||
import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { isLocalAi, UserAvatar } from '@renderer/config/env'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
@@ -22,7 +22,7 @@ const Sidebar: FC = () => {
|
||||
const { generating } = useRuntime()
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { windowStyle } = useSettings()
|
||||
const { windowStyle, showMinappIcon, showFilesIcon } = useSettings()
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
|
||||
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||
@@ -79,20 +79,31 @@ const Sidebar: FC = () => {
|
||||
</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 />
|
||||
{showMinappIcon && (
|
||||
<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('knowledge_base.title')} mouseEnterDelay={0.5} placement="right">
|
||||
<StyledLink onClick={() => to('/knowledge')}>
|
||||
<Icon className={isRoute('/knowledge')}>
|
||||
<FileSearchOutlined />
|
||||
</Icon>
|
||||
</StyledLink>
|
||||
</Tooltip>
|
||||
{showFilesIcon && (
|
||||
<Tooltip title={t('files.title')} mouseEnterDelay={0.8} placement="right">
|
||||
<StyledLink onClick={() => to('/files')}>
|
||||
<Icon className={isRoute('/files')}>
|
||||
<FolderOutlined />
|
||||
</Icon>
|
||||
</StyledLink>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Menus>
|
||||
</MainMenus>
|
||||
<Menus onClick={MinApp.onClose}>
|
||||
|
||||
@@ -3,97 +3,8 @@ export const DEFAULT_CONTEXTCOUNT = 5
|
||||
export const DEFAULT_MAX_TOKENS = 4096
|
||||
export const FONT_FAMILY =
|
||||
"Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
|
||||
|
||||
export const platform = window.electron?.process?.platform
|
||||
export const isMac = platform === 'darwin'
|
||||
export const isWindows = platform === 'win32' || platform === 'win64'
|
||||
export const isLinux = platform === 'linux'
|
||||
|
||||
export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
|
||||
export const textExts = [
|
||||
'.txt', // 普通文本文件
|
||||
'.md', // Markdown 文件
|
||||
'.mdx', // Markdown 文件
|
||||
'.html', // HTML 文件
|
||||
'.htm', // HTML 文件的另一种扩展名
|
||||
'.xml', // XML 文件
|
||||
'.json', // JSON 文件
|
||||
'.yaml', // YAML 文件
|
||||
'.yml', // YAML 文件的另一种扩展名
|
||||
'.csv', // 逗号分隔值文件
|
||||
'.tsv', // 制表符分隔值文件
|
||||
'.ini', // 配置文件
|
||||
'.log', // 日志文件
|
||||
'.rtf', // 富文本格式文件
|
||||
'.tex', // LaTeX 文件
|
||||
'.srt', // 字幕文件
|
||||
'.xhtml', // XHTML 文件
|
||||
'.nfo', // 信息文件(主要用于场景发布)
|
||||
'.conf', // 配置文件
|
||||
'.config', // 配置文件
|
||||
'.env', // 环境变量文件
|
||||
'.rst', // reStructuredText 文件
|
||||
'.php', // PHP 脚本文件,包含嵌入的 HTML
|
||||
'.js', // JavaScript 文件(部分是文本,部分可能包含代码)
|
||||
'.ts', // TypeScript 文件
|
||||
'.jsp', // JavaServer Pages 文件
|
||||
'.aspx', // ASP.NET 文件
|
||||
'.bat', // Windows 批处理文件
|
||||
'.sh', // Unix/Linux Shell 脚本文件
|
||||
'.py', // Python 脚本文件
|
||||
'.rb', // Ruby 脚本文件
|
||||
'.pl', // Perl 脚本文件
|
||||
'.sql', // SQL 脚本文件
|
||||
'.css', // Cascading Style Sheets 文件
|
||||
'.less', // Less CSS 预处理器文件
|
||||
'.scss', // Sass CSS 预处理器文件
|
||||
'.sass', // Sass 文件
|
||||
'.styl', // Stylus CSS 预处理器文件
|
||||
'.coffee', // CoffeeScript 文件
|
||||
'.ino', // Arduino 代码文件
|
||||
'.asm', // Assembly 语言文件
|
||||
'.go', // Go 语言文件
|
||||
'.scala', // Scala 语言文件
|
||||
'.swift', // Swift 语言文件
|
||||
'.kt', // Kotlin 语言文件
|
||||
'.rs', // Rust 语言文件
|
||||
'.lua', // Lua 语言文件
|
||||
'.groovy', // Groovy 语言文件
|
||||
'.dart', // Dart 语言文件
|
||||
'.hs', // Haskell 语言文件
|
||||
'.clj', // Clojure 语言文件
|
||||
'.cljs', // ClojureScript 语言文件
|
||||
'.elm', // Elm 语言文件
|
||||
'.erl', // Erlang 语言文件
|
||||
'.ex', // Elixir 语言文件
|
||||
'.exs', // Elixir 脚本文件
|
||||
'.pug', // Pug (formerly Jade) 模板文件
|
||||
'.haml', // Haml 模板文件
|
||||
'.slim', // Slim 模板文件
|
||||
'.tpl', // 模板文件(通用)
|
||||
'.ejs', // Embedded JavaScript 模板文件
|
||||
'.hbs', // Handlebars 模板文件
|
||||
'.mustache', // Mustache 模板文件
|
||||
'.jade', // Jade 模板文件 (已重命名为 Pug)
|
||||
'.twig', // Twig 模板文件
|
||||
'.blade', // Blade 模板文件 (Laravel)
|
||||
'.vue', // Vue.js 单文件组件
|
||||
'.jsx', // React JSX 文件
|
||||
'.tsx', // React TSX 文件
|
||||
'.graphql', // GraphQL 查询语言文件
|
||||
'.gql', // GraphQL 查询语言文件
|
||||
'.proto', // Protocol Buffers 文件
|
||||
'.thrift', // Thrift 文件
|
||||
'.toml', // TOML 配置文件
|
||||
'.edn', // Clojure 数据表示文件
|
||||
'.cake', // CakePHP 配置文件
|
||||
'.ctp', // CakePHP 视图文件
|
||||
'.cfm', // ColdFusion 标记语言文件
|
||||
'.cfc', // ColdFusion 组件文件
|
||||
'.m', // Objective-C 源文件
|
||||
'.mm', // Objective-C++ 源文件
|
||||
'.gradle', // Gradle 构建文件
|
||||
'.groovy', // Gradle 构建文件
|
||||
'.kts', // Kotlin Script 文件
|
||||
'.java' // Java 代码文件
|
||||
]
|
||||
|
||||
@@ -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 BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp'
|
||||
import BoltAppLogo from '@renderer/assets/images/apps/bolt.svg'
|
||||
@@ -8,14 +6,18 @@ import DoubaoAppLogo from '@renderer/assets/images/apps/doubao.png'
|
||||
import DuckDuckGoAppLogo from '@renderer/assets/images/apps/duckduckgo.webp'
|
||||
import FeloAppLogo from '@renderer/assets/images/apps/felo.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 HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg'
|
||||
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg'
|
||||
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 PoeAppLogo from '@renderer/assets/images/apps/poe.webp'
|
||||
import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png'
|
||||
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.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 WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg'
|
||||
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png'
|
||||
@@ -119,19 +121,6 @@ const _apps: MinAppType[] = [
|
||||
url: 'https://claude.ai/',
|
||||
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',
|
||||
name: '文心一言',
|
||||
@@ -210,6 +199,12 @@ const _apps: MinAppType[] = [
|
||||
url: 'https://felo.ai/',
|
||||
bodered: true
|
||||
},
|
||||
{
|
||||
id: 'duckduckgo',
|
||||
name: 'DuckDuckGo',
|
||||
logo: DuckDuckGoAppLogo,
|
||||
url: 'https://duck.ai'
|
||||
},
|
||||
{
|
||||
id: 'bolt',
|
||||
name: 'bolt',
|
||||
@@ -218,10 +213,30 @@ const _apps: MinAppType[] = [
|
||||
bodered: true
|
||||
},
|
||||
{
|
||||
id: 'duckduckgo',
|
||||
name: 'DuckDuckGo',
|
||||
logo: DuckDuckGoAppLogo,
|
||||
url: 'https://duck.ai'
|
||||
id: 'nm',
|
||||
name: '纳米AI搜索',
|
||||
logo: NamiAiSearchLogo,
|
||||
url: 'https://www.n.cn/',
|
||||
bodered: true
|
||||
},
|
||||
{
|
||||
id: 'thinkany',
|
||||
name: 'ThinkAny',
|
||||
logo: ThinkAnyLogo,
|
||||
url: 'https://thinkany.ai/',
|
||||
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/'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import AisingaporeModelLogo from '@renderer/assets/images/models/aisingapore.png
|
||||
import AisingaporeModelLogoDark from '@renderer/assets/images/models/aisingapore_dark.png'
|
||||
import BaichuanModelLogo from '@renderer/assets/images/models/baichuan.png'
|
||||
import BaichuanModelLogoDark from '@renderer/assets/images/models/baichuan_dark.png'
|
||||
import BigcodeModelLogo from '@renderer/assets/images/models/bigcode.png'
|
||||
import BigcodeModelLogoDark from '@renderer/assets/images/models/bigcode_dark.png'
|
||||
import BigcodeModelLogo from '@renderer/assets/images/models/bigcode.webp'
|
||||
import BigcodeModelLogoDark from '@renderer/assets/images/models/bigcode_dark.webp'
|
||||
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.png'
|
||||
import ChatGLMModelLogoDark from '@renderer/assets/images/models/chatglm_dark.png'
|
||||
import ChatGptModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
||||
@@ -121,6 +121,7 @@ import WenxinModelLogo from '@renderer/assets/images/models/wenxin.png'
|
||||
import WenxinModelLogoDark from '@renderer/assets/images/models/wenxin_dark.png'
|
||||
import YiModelLogo from '@renderer/assets/images/models/yi.png'
|
||||
import YiModelLogoDark from '@renderer/assets/images/models/yi_dark.png'
|
||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||
import { Model } from '@renderer/types'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
@@ -136,7 +137,7 @@ const visionAllowedModels = [
|
||||
'qwen-vl',
|
||||
'qwen2-vl',
|
||||
'internvl2',
|
||||
'grok',
|
||||
'grok-vision-beta',
|
||||
'pixtral',
|
||||
'gpt-4(?:-[\\w-]+)',
|
||||
'gpt-4o(?:-[\\w-]+)?',
|
||||
@@ -150,9 +151,9 @@ export const VISION_REGEX = new RegExp(
|
||||
'i'
|
||||
)
|
||||
|
||||
const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
|
||||
const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|base|retrieval|uae-)/i
|
||||
const NOT_SUPPORTED_REGEX = /(?:^text-|embed|tts|rerank|whisper|speech|davinci|babbage|bge-|base|retrieval|uae-)/i
|
||||
export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview/i
|
||||
export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina)/i
|
||||
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
|
||||
|
||||
export function getModelLogo(modelId: string) {
|
||||
const isLight = true
|
||||
@@ -165,7 +166,7 @@ export function getModelLogo(modelId: string) {
|
||||
pixtral: isLight ? PixtralModelLogo : PixtralModelLogoDark,
|
||||
jina: isLight ? JinaModelLogo : JinaModelLogoDark,
|
||||
abab: isLight ? MinimaxModelLogo : MinimaxModelLogoDark,
|
||||
'o1-': isLight ? ChatGPTo1ModelLogo : ChatGPTo1ModelLogoDark,
|
||||
o1: isLight ? ChatGPTo1ModelLogo : ChatGPTo1ModelLogoDark,
|
||||
'gpt-3': isLight ? ChatGPT35ModelLogo : ChatGPT35ModelLogoDark,
|
||||
'gpt-4': isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
||||
gpts: isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark,
|
||||
@@ -264,22 +265,10 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
ollama: [],
|
||||
silicon: [
|
||||
{
|
||||
id: 'Qwen/Qwen2.5-72B-Instruct',
|
||||
id: 'deepseek-ai/DeepSeek-V2.5',
|
||||
name: 'deepseek-ai/DeepSeek-V2.5',
|
||||
provider: 'silicon',
|
||||
name: 'Qwen2.5-72B-Instruct',
|
||||
group: 'Qwen2.5'
|
||||
},
|
||||
{
|
||||
id: 'Qwen/Qwen2.5-32B-Instruct',
|
||||
provider: 'silicon',
|
||||
name: 'Qwen2.5-32B-Instruct',
|
||||
group: 'Qwen2.5'
|
||||
},
|
||||
{
|
||||
id: 'Qwen/Qwen2.5-14B-Instruct',
|
||||
provider: 'silicon',
|
||||
name: 'Qwen2.5-14B-Instruct',
|
||||
group: 'Qwen2.5'
|
||||
group: 'deepseek-ai'
|
||||
},
|
||||
{
|
||||
id: 'Qwen/Qwen2.5-7B-Instruct',
|
||||
@@ -288,79 +277,17 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
group: 'Qwen2.5'
|
||||
},
|
||||
{
|
||||
id: 'Qwen/Qwen2-7B-Instruct',
|
||||
id: 'meta-llama/Llama-3.3-70B-Instruct',
|
||||
name: 'meta-llama/Llama-3.3-70B-Instruct',
|
||||
provider: 'silicon',
|
||||
name: 'Qwen2-7B-Instruct',
|
||||
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'
|
||||
group: 'meta-llama'
|
||||
}
|
||||
],
|
||||
openai: [
|
||||
{
|
||||
id: 'gpt-4o',
|
||||
provider: 'openai',
|
||||
name: ' GPT-4o',
|
||||
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'
|
||||
}
|
||||
{ id: 'gpt-4o', provider: 'openai', name: ' GPT-4o', group: 'GPT 4o' },
|
||||
{ id: 'gpt-4o-mini', provider: 'openai', name: ' GPT-4o-mini', group: 'GPT 4o' },
|
||||
{ id: 'o1-mini', provider: 'openai', name: ' o1-mini', group: 'o1' },
|
||||
{ id: 'o1-preview', provider: 'openai', name: ' o1-preview', group: 'o1' }
|
||||
],
|
||||
'azure-openai': [
|
||||
{
|
||||
@@ -384,10 +311,10 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
group: 'Gemini 1.5'
|
||||
},
|
||||
{
|
||||
id: 'gemini-1.5-pro-exp-0801',
|
||||
id: 'gemini-1.5-pro',
|
||||
name: 'Gemini 1.5 Pro',
|
||||
provider: 'gemini',
|
||||
name: 'Gemini 1.5 Pro Experimental 0801',
|
||||
group: 'Gemini 1.5'
|
||||
group: 'gemini-1.5'
|
||||
}
|
||||
],
|
||||
anthropic: [
|
||||
@@ -587,42 +514,10 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
}
|
||||
],
|
||||
yi: [
|
||||
{
|
||||
id: 'yi-large',
|
||||
provider: 'yi',
|
||||
name: 'Yi-Large',
|
||||
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'
|
||||
}
|
||||
{ id: 'yi-lightning', name: 'yi-lightning', provider: 'yi', group: 'yi-lightning', owned_by: '01.ai' },
|
||||
{ id: 'yi-medium', name: 'yi-medium', provider: 'yi', group: 'yi-medium', owned_by: '01.ai' },
|
||||
{ id: 'yi-large', name: 'yi-large', provider: 'yi', group: 'yi-large', owned_by: '01.ai' },
|
||||
{ id: 'yi-vision', name: 'yi-vision', provider: 'yi', group: 'yi-vision', owned_by: '01.ai' }
|
||||
],
|
||||
zhipu: [
|
||||
{
|
||||
@@ -676,22 +571,11 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
],
|
||||
moonshot: [
|
||||
{
|
||||
id: 'moonshot-v1-8k',
|
||||
id: 'moonshot-v1-auto',
|
||||
name: 'moonshot-v1-auto',
|
||||
provider: 'moonshot',
|
||||
name: 'Moonshot V1 8k',
|
||||
group: 'Moonshot V1'
|
||||
},
|
||||
{
|
||||
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'
|
||||
group: 'moonshot-v1',
|
||||
owned_by: 'moonshot'
|
||||
}
|
||||
],
|
||||
baichuan: [
|
||||
@@ -715,24 +599,11 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
}
|
||||
],
|
||||
bailian: [
|
||||
{
|
||||
id: 'qwen-turbo',
|
||||
provider: 'dashscope',
|
||||
name: 'Qwen Turbo',
|
||||
group: 'Qwen'
|
||||
},
|
||||
{
|
||||
id: 'qwen-plus',
|
||||
provider: 'dashscope',
|
||||
name: 'Qwen Plus',
|
||||
group: 'Qwen'
|
||||
},
|
||||
{
|
||||
id: 'qwen-max',
|
||||
provider: 'dashscope',
|
||||
name: 'Qwen Max',
|
||||
group: 'Qwen'
|
||||
}
|
||||
{ id: 'qwen-vl-plus', name: 'qwen-vl-plus', provider: 'dashscope', group: 'qwen-vl', owned_by: 'system' },
|
||||
{ id: 'qwen-coder-plus', name: 'qwen-coder-plus', provider: 'dashscope', group: 'qwen-coder', owned_by: 'system' },
|
||||
{ id: 'qwen-turbo', name: 'qwen-turbo', provider: 'dashscope', group: 'qwen-turbo', owned_by: 'system' },
|
||||
{ id: 'qwen-plus', name: 'qwen-plus', provider: 'dashscope', group: 'qwen-plus', owned_by: 'system' },
|
||||
{ id: 'qwen-max', name: 'qwen-max', provider: 'dashscope', group: 'qwen-max', owned_by: 'system' }
|
||||
],
|
||||
stepfun: [
|
||||
{
|
||||
@@ -807,6 +678,12 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
provider: 'grok',
|
||||
name: 'Grok Beta',
|
||||
group: 'Grok'
|
||||
},
|
||||
{
|
||||
id: 'grok-vision-beta',
|
||||
provider: 'grok',
|
||||
name: 'Grok Vision Beta',
|
||||
group: 'Grok'
|
||||
}
|
||||
],
|
||||
mistral: [
|
||||
@@ -823,7 +700,56 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
group: 'Mistral'
|
||||
}
|
||||
],
|
||||
jina: [],
|
||||
jina: [
|
||||
{
|
||||
id: 'jina-clip-v1',
|
||||
provider: 'jina',
|
||||
name: 'jina-clip-v1',
|
||||
group: 'Jina Clip'
|
||||
},
|
||||
{
|
||||
id: 'jina-clip-v2',
|
||||
provider: 'jina',
|
||||
name: 'jina-clip-v2',
|
||||
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'
|
||||
}
|
||||
],
|
||||
aihubmix: [
|
||||
{
|
||||
id: 'gpt-4o-mini',
|
||||
@@ -1040,13 +966,43 @@ export function isTextToImageModel(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 {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
|
||||
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
||||
}
|
||||
|
||||
export function isSupportedModel(model: OpenAI.Models.Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !NOT_SUPPORTED_REGEX.test(model.id)
|
||||
}
|
||||
|
||||
export function isWebSearchModel(model: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
|
||||
const provider = getProviderByModel(model)
|
||||
|
||||
if (!provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (provider.id === 'gemini' || provider?.type === 'gemini') && model?.id === 'gemini-2.0-flash-exp'
|
||||
}
|
||||
|
||||
@@ -45,7 +45,24 @@ export const AGENT_PROMPT = `
|
||||
`
|
||||
|
||||
export const SUMMARIZE_PROMPT =
|
||||
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不要使用标点符号和其他特殊符号。'
|
||||
'你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号'
|
||||
|
||||
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: {
|
||||
api: {
|
||||
url: 'https://aihubmix.com'
|
||||
url: 'https://aihubmix.com?aff=SJyh'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://aihubmix.com/',
|
||||
apiKey: 'https://aihubmix.com/token',
|
||||
official: 'https://aihubmix.com?aff=SJyh',
|
||||
apiKey: 'https://aihubmix.com?aff=SJyh',
|
||||
docs: 'https://doc.aihubmix.com/',
|
||||
models: 'https://aihubmix.com/models'
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { LanguageVarious } from '@renderer/types'
|
||||
import { ConfigProvider, theme } from 'antd'
|
||||
import enUS from 'antd/locale/en_US'
|
||||
import jaJP from 'antd/locale/ja_JP'
|
||||
import ruRU from 'antd/locale/ru_RU'
|
||||
import zhCN from 'antd/locale/zh_CN'
|
||||
import zhTW from 'antd/locale/zh_TW'
|
||||
@@ -31,6 +32,13 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
Menu: {
|
||||
activeBarBorderWidth: 0,
|
||||
darkItemBg: 'transparent'
|
||||
},
|
||||
Button: {
|
||||
boxShadow: 'none',
|
||||
boxShadowSecondary: 'none',
|
||||
defaultShadow: 'none',
|
||||
dangerShadow: 'none',
|
||||
primaryShadow: 'none'
|
||||
}
|
||||
},
|
||||
token: {
|
||||
@@ -52,6 +60,8 @@ function getAntdLocale(language: LanguageVarious) {
|
||||
return enUS
|
||||
case 'ru-RU':
|
||||
return ruRU
|
||||
case 'ja-JP':
|
||||
return jaJP
|
||||
|
||||
default:
|
||||
return zhCN
|
||||
|
||||
@@ -54,13 +54,15 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
|
||||
const codeToHtml = async (code: string, language: string) => {
|
||||
if (!highlighter) return ''
|
||||
|
||||
const escapedCode = code.replace(/[<>]/g, (char) => ({ '<': '<', '>': '>' })[char]!)
|
||||
|
||||
try {
|
||||
if (!highlighter.getLoadedLanguages().includes(language as BundledLanguage)) {
|
||||
if (language in bundledLanguages) {
|
||||
if (language in bundledLanguages || language === 'text') {
|
||||
await highlighter.loadLanguage(language as BundledLanguage)
|
||||
console.log(`Loaded language: ${language}`)
|
||||
} else {
|
||||
return `<pre><code>${code}</code></pre>`
|
||||
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +72,7 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(`Error highlighting code for language '${language}':`, error)
|
||||
return `<pre><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 { populateTopics } from './populate'
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const db = new Dexie('CherryStudio') as Dexie & {
|
||||
files: EntityTable<FileType, 'id'>
|
||||
topics: EntityTable<Pick<Topic, 'id' | 'messages'>, 'id'>
|
||||
settings: EntityTable<{ id: string; value: any }, 'id'>
|
||||
knowledge_notes: EntityTable<KnowledgeItem, 'id'>
|
||||
}
|
||||
|
||||
db.version(1).stores({
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count'
|
||||
})
|
||||
|
||||
db.version(2)
|
||||
.stores({
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id, messages',
|
||||
settings: '&id, value'
|
||||
})
|
||||
.upgrade(populateTopics)
|
||||
db.version(2).stores({
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id, messages',
|
||||
settings: '&id, value'
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
keyv: KeyvStorage
|
||||
mermaid: any
|
||||
store: any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,36 @@ import { isMac } from '@renderer/config/constant'
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setAvatar, setFilesPath } from '@renderer/store/runtime'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime'
|
||||
import { delay, runAsyncFunction } from '@renderer/utils'
|
||||
import { useLiveQuery } from 'dexie-react-hooks'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useDefaultModel } from './useAssistant'
|
||||
import { useRuntime } from './useRuntime'
|
||||
import { useSettings } from './useSettings'
|
||||
import useUpdateHandler from './useUpdateHandler'
|
||||
|
||||
export function useAppInit() {
|
||||
const dispatch = useAppDispatch()
|
||||
const { proxyUrl, language, windowStyle, manualUpdateCheck, proxyMode } = useSettings()
|
||||
const {
|
||||
proxyUrl,
|
||||
language,
|
||||
windowStyle,
|
||||
manualUpdateCheck,
|
||||
proxyMode,
|
||||
webdavAutoSync,
|
||||
webdavSyncInterval,
|
||||
customCss
|
||||
} = useSettings()
|
||||
const { minappShow } = useRuntime()
|
||||
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||
|
||||
useUpdateHandler()
|
||||
|
||||
useEffect(() => {
|
||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||
}, [avatar, dispatch])
|
||||
@@ -28,11 +41,12 @@ export function useAppInit() {
|
||||
runAsyncFunction(async () => {
|
||||
const { isPackaged } = await window.api.getAppInfo()
|
||||
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(() => {
|
||||
if (proxyMode === 'system') {
|
||||
@@ -69,4 +83,26 @@ export function useAppInit() {
|
||||
dispatch(setFilesPath(info.filesPath))
|
||||
})
|
||||
}, [dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
webdavAutoSync ? startAutoSync() : stopAutoSync()
|
||||
}, [webdavAutoSync, webdavSyncInterval])
|
||||
|
||||
useEffect(() => {
|
||||
import('@renderer/queue/KnowledgeQueue')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const oldCustomCss = document.getElementById('user-defined-custom-css')
|
||||
if (oldCustomCss) {
|
||||
oldCustomCss.remove()
|
||||
}
|
||||
|
||||
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 { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
@@ -50,8 +51,20 @@ export function useAssistant(id: string) {
|
||||
dispatch(removeTopic({ assistantId: assistant.id, topic }))
|
||||
},
|
||||
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 }))
|
||||
// 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 })),
|
||||
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
|
||||
|
||||
279
src/renderer/src/hooks/useKnowledge.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
/* 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,
|
||||
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 }))
|
||||
}
|
||||
setTimeout(() => KnowledgeQueue.checkAllBases(), 0)
|
||||
}
|
||||
|
||||
// 获取笔记内容
|
||||
const getNoteContent = async (noteId: string) => {
|
||||
return await db.knowledge_notes.get(noteId)
|
||||
}
|
||||
|
||||
// 移除项目
|
||||
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 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,
|
||||
updateItemStatus,
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { uuid } from '@renderer/utils'
|
||||
export function usePaintings() {
|
||||
const paintings = useAppSelector((state) => state.paintings.paintings)
|
||||
const dispatch = useAppDispatch()
|
||||
const generateRandomSeed = () => Math.floor(Math.random() * 1000000).toString()
|
||||
|
||||
return {
|
||||
paintings,
|
||||
@@ -20,7 +21,7 @@ export function usePaintings() {
|
||||
negativePrompt: '',
|
||||
imageSize: '1024x1024',
|
||||
numImages: 1,
|
||||
seed: '',
|
||||
seed: generateRandomSeed(),
|
||||
steps: 25,
|
||||
guidanceScale: 4.5,
|
||||
model: TEXT_TO_IMAGES_MODELS[0].id
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
SendMessageShortcut,
|
||||
setSendMessageShortcut as _setSendMessageShortcut,
|
||||
setTheme,
|
||||
SettingsState,
|
||||
setTopicPosition,
|
||||
setTray,
|
||||
setWindowStyle
|
||||
@@ -41,3 +42,7 @@ export function useMessageStyle() {
|
||||
isBubbleStyle
|
||||
}
|
||||
}
|
||||
|
||||
export const getStoreSetting = (key: keyof SettingsState) => {
|
||||
return store.getState().settings[key]
|
||||
}
|
||||
|
||||
90
src/renderer/src/hooks/useShortcuts.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { useCallback } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
interface UseShortcutOptions {
|
||||
preventDefault?: boolean
|
||||
enableOnFormTags?: boolean
|
||||
enabled?: boolean
|
||||
description?: string
|
||||
}
|
||||
|
||||
const defaultOptions: UseShortcutOptions = {
|
||||
preventDefault: true,
|
||||
enableOnFormTags: true,
|
||||
enabled: true
|
||||
}
|
||||
|
||||
export const useShortcut = (
|
||||
shortcutKey: string,
|
||||
callback: (e: KeyboardEvent) => void,
|
||||
options: UseShortcutOptions = defaultOptions
|
||||
) => {
|
||||
const shortcuts = useAppSelector((state) => state.shortcuts.shortcuts)
|
||||
|
||||
const formatShortcut = useCallback((shortcut: string[]) => {
|
||||
return shortcut
|
||||
.map((key) => {
|
||||
switch (key.toLowerCase()) {
|
||||
case 'command':
|
||||
return 'meta'
|
||||
default:
|
||||
return key.toLowerCase()
|
||||
}
|
||||
})
|
||||
.join('+')
|
||||
}, [])
|
||||
|
||||
const shortcutConfig = shortcuts.find((s) => s.key === shortcutKey)
|
||||
|
||||
useHotkeys(
|
||||
shortcutConfig?.enabled ? formatShortcut(shortcutConfig.shortcut) : 'none',
|
||||
(e) => {
|
||||
if (options.preventDefault) {
|
||||
e.preventDefault()
|
||||
}
|
||||
if (options.enabled !== false) {
|
||||
callback(e)
|
||||
}
|
||||
},
|
||||
{
|
||||
enableOnFormTags: options.enableOnFormTags,
|
||||
description: options.description || shortcutConfig?.key,
|
||||
enabled: !!shortcutConfig?.enabled
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function useShortcuts() {
|
||||
const shortcuts = useAppSelector((state) => state.shortcuts.shortcuts)
|
||||
return { shortcuts }
|
||||
}
|
||||
|
||||
export function useShortcutDisplay(key: string) {
|
||||
const formatShortcut = useCallback((shortcut: string[]) => {
|
||||
return shortcut
|
||||
.map((key) => {
|
||||
switch (key.toLowerCase()) {
|
||||
case 'control':
|
||||
return isMac ? '⌃' : 'Ctrl'
|
||||
case 'ctrl':
|
||||
return isMac ? '⌃' : 'Ctrl'
|
||||
case 'command':
|
||||
return isMac ? '⌘' : isWindows ? 'Win' : 'Super'
|
||||
case 'alt':
|
||||
return isMac ? '⌥' : 'Alt'
|
||||
case 'shift':
|
||||
return isMac ? '⇧' : 'Shift'
|
||||
case 'commandorcontrol':
|
||||
return isMac ? '⌘' : 'Ctrl'
|
||||
default:
|
||||
return key.charAt(0).toUpperCase() + key.slice(1).toLowerCase()
|
||||
}
|
||||
})
|
||||
.join('+')
|
||||
}, [])
|
||||
const shortcuts = useAppSelector((state) => state.shortcuts.shortcuts)
|
||||
const shortcutConfig = shortcuts.find((s) => s.key === key)
|
||||
return shortcutConfig?.enabled ? formatShortcut(shortcutConfig.shortcut) : ''
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setShowTopics, toggleShowAssistants, toggleShowTopics } from '@renderer/store/settings'
|
||||
import { setShowAssistants, setShowTopics, toggleShowAssistants, toggleShowTopics } from '@renderer/store/settings'
|
||||
|
||||
export function useShowAssistants() {
|
||||
const showAssistants = useAppSelector((state) => state.settings.showAssistants)
|
||||
@@ -7,6 +7,7 @@ export function useShowAssistants() {
|
||||
|
||||
return {
|
||||
showAssistants,
|
||||
setShowAssistants: (show: boolean) => dispatch(setShowAssistants(show)),
|
||||
toggleShowAssistants: () => dispatch(toggleShowAssistants())
|
||||
}
|
||||
}
|
||||
|
||||
68
src/renderer/src/hooks/useUpdateHandler.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setUpdateState } from '@renderer/store/runtime'
|
||||
import type { ProgressInfo, UpdateInfo } from 'electron-updater'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function useUpdateHandler() {
|
||||
const dispatch = useAppDispatch()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
const ipcRenderer = window.electron.ipcRenderer
|
||||
const removers = [
|
||||
ipcRenderer.on('update-not-available', () => {
|
||||
dispatch(setUpdateState({ checking: false }))
|
||||
if (window.location.hash.includes('settings/about')) {
|
||||
window.message.success(t('settings.about.updateNotAvailable'))
|
||||
}
|
||||
}),
|
||||
ipcRenderer.on('update-available', (_, releaseInfo: UpdateInfo) => {
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
checking: false,
|
||||
downloading: true,
|
||||
info: releaseInfo,
|
||||
available: true
|
||||
})
|
||||
)
|
||||
}),
|
||||
ipcRenderer.on('download-update', () => {
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
checking: false,
|
||||
downloading: true
|
||||
})
|
||||
)
|
||||
}),
|
||||
ipcRenderer.on('download-progress', (_, progress: ProgressInfo) => {
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
downloading: progress.percent < 100,
|
||||
downloadProgress: progress.percent
|
||||
})
|
||||
)
|
||||
}),
|
||||
ipcRenderer.on('update-downloaded', () => {
|
||||
dispatch(setUpdateState({ downloading: false }))
|
||||
}),
|
||||
ipcRenderer.on('update-error', (_, error) => {
|
||||
dispatch(
|
||||
setUpdateState({
|
||||
checking: false,
|
||||
downloading: false,
|
||||
downloadProgress: 0
|
||||
})
|
||||
)
|
||||
if (window.location.hash.includes('settings/about')) {
|
||||
window.modal.info({
|
||||
title: t('settings.about.updateError'),
|
||||
content: error?.message || t('settings.about.updateError'),
|
||||
icon: null
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
return () => removers.forEach((remover) => remover())
|
||||
}, [dispatch, t])
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import enUS from './locales/en-us.json'
|
||||
import jaJP from './locales/ja-jp.json'
|
||||
import ruRU from './locales/ru-ru.json'
|
||||
import zhCN from './locales/zh-cn.json'
|
||||
import zhTW from './locales/zh-tw.json'
|
||||
@@ -10,6 +11,7 @@ const resources = {
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
'ja-JP': jaJP,
|
||||
'ru-RU': ruRU
|
||||
}
|
||||
|
||||
|
||||
587
src/renderer/src/i18n/locales/ja-jp.json
Normal file
@@ -0,0 +1,587 @@
|
||||
{
|
||||
"translation": {
|
||||
"agents": {
|
||||
"add.button": "アシスタントに追加",
|
||||
"add.name": "名前",
|
||||
"add.name.placeholder": "名前を入力",
|
||||
"add.prompt": "プロンプト",
|
||||
"add.prompt.placeholder": "プロンプトを入力",
|
||||
"add.title": "エージェントを作成",
|
||||
"delete.popup.content": "このエージェントを削除してもよろしいですか?",
|
||||
"edit.message.add.title": "追加",
|
||||
"edit.message.assistant.placeholder": "アシスタントのメッセージを入力",
|
||||
"edit.message.assistant.title": "アシスタント",
|
||||
"edit.message.empty.content": "会話の入力内容が空です",
|
||||
"edit.message.group.title": "メッセージグループ",
|
||||
"edit.message.title": "プリセットメッセージ",
|
||||
"edit.message.user.placeholder": "ユーザーメッセージを入力",
|
||||
"edit.message.user.title": "ユーザー",
|
||||
"edit.model.select.title": "モデルを選択",
|
||||
"edit.settings.hide_preset_messages": "プリセットメッセージを非表示",
|
||||
"edit.title": "エージェントを編集",
|
||||
"manage.title": "エージェントを管理",
|
||||
"my_agents": "マイエージェント",
|
||||
"search.no_results": "結果が見つかりません",
|
||||
"sorting.title": "並び替え",
|
||||
"tag.agent": "エージェント",
|
||||
"tag.default": "デフォルト",
|
||||
"tag.new": "新規",
|
||||
"tag.system": "システム",
|
||||
"title": "エージェント"
|
||||
},
|
||||
"assistants": {
|
||||
"abbr": "アシスタント",
|
||||
"clear.content": "トピックをクリアすると、アシスタント内のすべてのトピックとファイルが削除されます。続行しますか?",
|
||||
"clear.title": "トピックをクリア",
|
||||
"copy.title": "アシスタントをコピー",
|
||||
"delete.content": "アシスタントを削除すると、そのアシスタントのすべてのトピックとファイルが削除されます。削除しますか?",
|
||||
"delete.title": "アシスタントを削除",
|
||||
"edit.title": "アシスタントを編集",
|
||||
"save.success": "保存に成功しました",
|
||||
"save.title": "エージェントに保存",
|
||||
"search": "アシスタントを検索...",
|
||||
"settings.auto_reset_model": "自動リセットモデル",
|
||||
"settings.auto_reset_model.tip": "新しいトピックを作成する際にモデルを自動的にリセットします",
|
||||
"settings.default_model": "デフォルトモデル",
|
||||
"settings.model": "モデル設定",
|
||||
"settings.preset_messages": "プリセットメッセージ",
|
||||
"settings.prompt": "プロンプト設定",
|
||||
"title": "アシスタント"
|
||||
},
|
||||
"button": {
|
||||
"add": "追加",
|
||||
"added": "追加済み",
|
||||
"collapse": "折りたたむ",
|
||||
"manage": "管理",
|
||||
"select_model": "モデルを選択",
|
||||
"show.all": "すべて表示"
|
||||
},
|
||||
"chat": {
|
||||
"add.assistant.title": "アシスタントを追加",
|
||||
"artifacts.button.download": "ダウンロード",
|
||||
"artifacts.button.preview": "プレビュー",
|
||||
"assistant.search.placeholder": "検索",
|
||||
"default.description": "こんにちは、私はデフォルトのアシスタントです。すぐにチャットを始められます。",
|
||||
"default.name": "⭐️ デフォルトアシスタント",
|
||||
"default.topic.name": "デフォルトトピック",
|
||||
"input.clear": "クリア",
|
||||
"input.clear.content": "現在のトピックのすべてのメッセージをクリアしますか?",
|
||||
"input.clear.title": "すべてのメッセージをクリアしますか?",
|
||||
"input.collapse": "折りたたむ",
|
||||
"input.context_count.tip": "コンテキスト数",
|
||||
"input.estimated_tokens.tip": "推定トークン数",
|
||||
"input.expand": "展開",
|
||||
"input.new.context": "コンテキストをクリア",
|
||||
"input.new_topic": "新しいトピック {{Command}}",
|
||||
"input.pause": "一時停止",
|
||||
"input.placeholder": "ここにメッセージを入力...",
|
||||
"input.send": "送信",
|
||||
"input.settings": "設定",
|
||||
"input.topics": " トピック ",
|
||||
"input.translate": "英語に翻訳",
|
||||
"input.upload": "画像またはドキュメントをアップロード",
|
||||
"input.web_search": "ウェブ検索を有効にする",
|
||||
"input.knowledge_base": "ナレッジベース",
|
||||
"message.new.branch": "新しいブランチ",
|
||||
"message.new.branch.created": "新しいブランチが作成されました",
|
||||
"message.regenerate.model": "モデルを切り替え",
|
||||
"message.new.context": "新しいコンテキスト",
|
||||
"save": "保存",
|
||||
"settings.code_collapsible": "コードブロックを折りたたむ",
|
||||
"settings.context_count": "コンテキスト",
|
||||
"settings.context_count.tip": "コンテキストに保持する以前のメッセージの数",
|
||||
"settings.max": "最大",
|
||||
"settings.max_tokens": "最大トークン制限を有効にする",
|
||||
"settings.max_tokens.tip": "モデルが生成できる最大トークン数。通常のチャットでは500-800、短いテキスト生成では800-2000、コード生成では2000-3600、長いテキスト生成では4000以上を推奨",
|
||||
"settings.reset": "リセット",
|
||||
"settings.set_as_default": "デフォルトのアシスタントに適用",
|
||||
"settings.show_line_numbers": "コードに行番号を表示",
|
||||
"settings.temperature": "温度",
|
||||
"settings.temperature.tip": "低い値はモデルをより創造的で予測不可能にし、高い値はより決定論的で正確にします",
|
||||
"settings.top_p": "Top-P",
|
||||
"settings.top_p.tip": "デフォルト値は1で、値が小さいほど回答の多様性が減り、理解しやすくなります。値が大きいほど、AIの語彙範囲が広がり、多様性が増します",
|
||||
"suggestions.title": "提案された質問",
|
||||
"topics.auto_rename": "自動リネーム",
|
||||
"topics.clear.title": "メッセージをクリア",
|
||||
"topics.edit.placeholder": "新しい名前を入力",
|
||||
"topics.edit.title": "名前を編集",
|
||||
"topics.export.image": "画像としてエクスポート",
|
||||
"topics.export.md": "Markdownとしてエクスポート",
|
||||
"topics.export.title": "エクスポート",
|
||||
"topics.export.word": "Wordとしてエクスポート",
|
||||
"topics.list": "トピックリスト",
|
||||
"topics.move_to": "移動先",
|
||||
"topics.title": "トピック",
|
||||
"translate": "翻訳",
|
||||
"resend": "再送信"
|
||||
},
|
||||
"common": {
|
||||
"and": "と",
|
||||
"assistant": "アシスタント",
|
||||
"avatar": "アバター",
|
||||
"back": "戻る",
|
||||
"cancel": "キャンセル",
|
||||
"chat": "チャット",
|
||||
"close": "閉じる",
|
||||
"copy": "コピー",
|
||||
"cut": "切り取り",
|
||||
"default": "デフォルト",
|
||||
"delete": "削除",
|
||||
"description": "説明",
|
||||
"docs": "ドキュメント",
|
||||
"download": "ダウンロード",
|
||||
"duplicate": "複製",
|
||||
"edit": "編集",
|
||||
"footnotes": "脚注",
|
||||
"language": "言語",
|
||||
"model": "モデル",
|
||||
"models": "モデル",
|
||||
"name": "名前",
|
||||
"paste": "貼り付け",
|
||||
"prompt": "プロンプト",
|
||||
"provider": "プロバイダー",
|
||||
"regenerate": "再生成",
|
||||
"rename": "名前を変更",
|
||||
"reset": "リセット",
|
||||
"save": "保存",
|
||||
"search": "検索",
|
||||
"select": "選択",
|
||||
"topics": "トピック",
|
||||
"warning": "警告",
|
||||
"you": "あなた",
|
||||
"clear": "クリア",
|
||||
"add": "追加"
|
||||
},
|
||||
"error": {
|
||||
"backup.file_format": "バックアップファイルの形式エラー",
|
||||
"chat.response": "エラーが発生しました。APIキーが設定されていない場合は、設定 > プロバイダーでキーを設定してください",
|
||||
"no_api_key": "APIキーが設定されていません",
|
||||
"provider_disabled": "モデルプロバイダーが有効になっていません",
|
||||
"render": {
|
||||
"title": "レンダリングエラー",
|
||||
"description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"assistant": "アシスタント",
|
||||
"attached_files": "添付ファイル",
|
||||
"conversation_details": "会話の詳細",
|
||||
"conversation_history": "会話履歴",
|
||||
"created": "作成日",
|
||||
"last_updated": "最終更新日",
|
||||
"messages": "メッセージ",
|
||||
"user": "ユーザー"
|
||||
},
|
||||
"files": {
|
||||
"actions": "操作",
|
||||
"all": "すべてのファイル",
|
||||
"count": "数",
|
||||
"created_at": "作成日",
|
||||
"document": "ドキュメント",
|
||||
"file": "ファイル",
|
||||
"image": "画像",
|
||||
"name": "名前",
|
||||
"open": "開く",
|
||||
"size": "サイズ",
|
||||
"text": "テキスト",
|
||||
"title": "ファイル"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "チャットを続ける",
|
||||
"locate.message": "メッセージを探す",
|
||||
"search.messages": "すべてのメッセージを検索",
|
||||
"search.placeholder": "トピックまたはメッセージを検索...",
|
||||
"search.topics.empty": "トピックが見つかりませんでした。Enterキーを押してすべてのメッセージを検索",
|
||||
"title": "トピック検索"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "アラビア語",
|
||||
"chinese": "中国語",
|
||||
"chinese-traditional": "繁体字中国語",
|
||||
"english": "英語",
|
||||
"french": "フランス語",
|
||||
"italian": "イタリア語",
|
||||
"japanese": "日本語",
|
||||
"korean": "韓国語",
|
||||
"portuguese": "ポルトガル語",
|
||||
"russian": "ロシア語",
|
||||
"spanish": "スペイン語"
|
||||
},
|
||||
"mermaid": {
|
||||
"download": {
|
||||
"png": "PNGをダウンロード",
|
||||
"svg": "SVGをダウンロード"
|
||||
},
|
||||
"tabs": {
|
||||
"preview": "プレビュー",
|
||||
"source": "ソース"
|
||||
},
|
||||
"title": "Mermaid図"
|
||||
},
|
||||
"message": {
|
||||
"api.connection.failed": "接続に失敗しました",
|
||||
"api.connection.success": "接続に成功しました",
|
||||
"assistant.added.content": "アシスタントが追加されました",
|
||||
"backup.failed": "バックアップに失敗しました",
|
||||
"backup.success": "バックアップに成功しました",
|
||||
"backup.start.success": "バックアップを開始しました",
|
||||
"chat.completion.paused": "チャットの完了が一時停止されました",
|
||||
"copied": "コピーしました!",
|
||||
"error.enter.api.host": "APIホストを入力してください",
|
||||
"error.enter.api.key": "APIキーを入力してください",
|
||||
"error.enter.model": "モデルを選択してください",
|
||||
"error.invalid.proxy.url": "無効なプロキシURL",
|
||||
"error.invalid.webdav": "無効なWebDAV設定",
|
||||
"message.code_style": "コードスタイル",
|
||||
"message.delete.content": "このメッセージを削除してもよろしいですか?",
|
||||
"message.delete.title": "メッセージを削除",
|
||||
"message.style": "メッセージスタイル",
|
||||
"message.style.bubble": "バブル",
|
||||
"message.style.plain": "プレーン",
|
||||
"reset.confirm.content": "すべてのデータをリセットしてもよろしいですか?",
|
||||
"reset.double.confirm.content": "すべてのデータが失われます。続行しますか?",
|
||||
"reset.double.confirm.title": "データが失われます!!!",
|
||||
"restore.success": "復元に成功しました",
|
||||
"save.success.title": "保存に成功しました",
|
||||
"switch.disabled": "アシスタントが生成中は切り替えが無効です",
|
||||
"topic.added": "新しいトピックが追加されました",
|
||||
"upgrade.success.button": "再起動",
|
||||
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
|
||||
"upgrade.success.title": "アップグレードに成功しました",
|
||||
"regenerate.confirm": "再生成すると現在のメッセージが置き換えられます",
|
||||
"copy.success": "コピーしました!"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "ミニアプリ"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
|
||||
"keep_alive_time.placeholder": "分",
|
||||
"keep_alive_time.title": "保持時間",
|
||||
"title": "Ollama"
|
||||
},
|
||||
"paintings": {
|
||||
"button.delete.image": "画像を削除",
|
||||
"button.delete.image.confirm": "この画像を削除してもよろしいですか?",
|
||||
"button.new.image": "新しい画像",
|
||||
"guidance_scale": "ガイダンススケール",
|
||||
"guidance_scale_tip": "分類器なしのガイダンス。モデルが関連する画像を探す際にプロンプトにどれだけ従うかを制御します",
|
||||
"image.size": "画像サイズ",
|
||||
"inference_steps": "推論ステップ数",
|
||||
"inference_steps_tip": "実行する推論ステップ数。ステップ数が多いほど品質が向上しますが、時間がかかります",
|
||||
"negative_prompt": "ネガティブプロンプト",
|
||||
"negative_prompt_tip": "画像に含めたくない内容を説明します",
|
||||
"number_images": "生成数",
|
||||
"number_images_tip": "生成する画像の数(1-4)",
|
||||
"prompt_placeholder": "作成したい画像を説明します。例:夕日の湖畔、遠くに山々",
|
||||
"regenerate.confirm": "これにより、既存の生成画像が置き換えられます。続行しますか?",
|
||||
"seed": "シード",
|
||||
"seed_tip": "同じシードとプロンプトで似た画像を生成できます",
|
||||
"title": "画像"
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "百川",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
"deepseek": "DeepSeek",
|
||||
"doubao": "豆包",
|
||||
"fireworks": "Fireworks",
|
||||
"gemini": "Gemini",
|
||||
"github": "GitHub Models",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
"hunyuan": "腾讯混元",
|
||||
"hyperbolic": "Hyperbolic",
|
||||
"jina": "Jina",
|
||||
"minimax": "MiniMax",
|
||||
"mistral": "Mistral",
|
||||
"moonshot": "月の暗面",
|
||||
"nvidia": "NVIDIA",
|
||||
"ocoolai": "ocoolAI",
|
||||
"ollama": "Ollama",
|
||||
"openai": "OpenAI",
|
||||
"openrouter": "OpenRouter",
|
||||
"silicon": "SiliconFlow",
|
||||
"stepfun": "StepFun",
|
||||
"together": "Together",
|
||||
"yi": "零一万物",
|
||||
"zhinao": "360智脳",
|
||||
"zhipu": "智譜AI"
|
||||
},
|
||||
"settings": {
|
||||
"about": "について",
|
||||
"about.checkUpdate": "更新を確認",
|
||||
"about.checkUpdate.available": "今すぐ更新",
|
||||
"about.checkingUpdate": "更新を確認中...",
|
||||
"about.contact.button": "メール",
|
||||
"about.contact.title": "連絡先",
|
||||
"about.description": "クリエイターのための強力なAIアシスタント",
|
||||
"about.downloading": "ダウンロード中...",
|
||||
"about.feedback.button": "フィードバック",
|
||||
"about.feedback.title": "フィードバック",
|
||||
"about.license.button": "ライセンス",
|
||||
"about.license.title": "ライセンス",
|
||||
"about.releases.button": "リリース",
|
||||
"about.releases.title": "リリースノート",
|
||||
"about.title": "について",
|
||||
"about.updateAvailable": "新しいバージョン {{version}} が見つかりました",
|
||||
"about.updateError": "更新エラー",
|
||||
"about.updateNotAvailable": "最新バージョンを使用しています",
|
||||
"about.website.button": "ウェブサイト",
|
||||
"about.website.title": "公式ウェブサイト",
|
||||
"about.social.title": "ソーシャルアカウント",
|
||||
"advanced.auto_switch_to_topics": "トピックに自動的に切り替える",
|
||||
"advanced.title": "詳細設定",
|
||||
"assistant": "デフォルトアシスタント",
|
||||
"assistant.model_params": "モデルパラメータ",
|
||||
"assistant.title": "デフォルトアシスタント",
|
||||
"data": {
|
||||
"app_data": "アプリデータ",
|
||||
"app_logs": "アプリログ",
|
||||
"clear_cache": {
|
||||
"button": "キャッシュをクリア",
|
||||
"confirm": "キャッシュをクリアすると、アプリのキャッシュデータ(ミニアプリデータを含む)が削除されます。この操作は元に戻せません。続行しますか?",
|
||||
"error": "キャッシュのクリアに失敗しました",
|
||||
"success": "キャッシュがクリアされました",
|
||||
"title": "キャッシュをクリア"
|
||||
},
|
||||
"data.title": "データディレクトリ",
|
||||
"title": "データ設定",
|
||||
"webdav.backup.button": "WebDAVにバックアップ",
|
||||
"webdav.host": "WebDAVホスト",
|
||||
"webdav.host.placeholder": "http://localhost:8080",
|
||||
"webdav.password": "WebDAVパスワード",
|
||||
"webdav.path": "WebDAVパス",
|
||||
"webdav.path.placeholder": "/backup",
|
||||
"webdav.autoSync": "自動同期",
|
||||
"webdav.minutes": "分",
|
||||
"webdav.restore.button": "WebDAVから復元",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAVユーザー"
|
||||
},
|
||||
"display.title": "表示設定",
|
||||
"font_size.title": "メッセージのフォントサイズ",
|
||||
"general": "一般設定",
|
||||
"general.backup.button": "バックアップ",
|
||||
"general.backup.title": "データのバックアップと復元",
|
||||
"general.manually_check_update.title": "更新チェックを無効にする",
|
||||
"general.reset.button": "リセット",
|
||||
"general.reset.title": "データをリセット",
|
||||
"general.restore.button": "復元",
|
||||
"general.title": "一般設定",
|
||||
"general.user_name": "ユーザー名",
|
||||
"general.user_name.placeholder": "ユーザー名を入力",
|
||||
"general.view_webdav_settings": "WebDAV設定を表示",
|
||||
"general.display.title": "表示設定",
|
||||
"display.sidebar.minapp.icon": "ミニアプリのアイコンを表示",
|
||||
"display.sidebar.files.icon": "ファイルのアイコンを表示",
|
||||
"display.sidebar.title": "サイドバー設定",
|
||||
"display.topic.title": "トピック設定",
|
||||
"display.custom.css": "カスタムCSS",
|
||||
"display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */",
|
||||
"input.auto_translate_with_space": "スペースを3回押して翻訳",
|
||||
"messages.divider": "メッセージ間に区切り線を表示",
|
||||
"messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け",
|
||||
"messages.input.send_shortcuts": "送信ショートカット",
|
||||
"messages.input.show_estimated_tokens": "推定トークン数を表示",
|
||||
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
||||
"messages.input.title": "入力設定",
|
||||
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
|
||||
"messages.math_engine": "数式エンジン",
|
||||
"messages.model.title": "モデル設定",
|
||||
"messages.title": "メッセージ設定",
|
||||
"messages.use_serif_font": "セリフフォントを使用",
|
||||
"messages.input.paste_long_text_threshold": "長いテキストの長さ",
|
||||
"model": "デフォルトモデル",
|
||||
"models.add.add_model": "モデルを追加",
|
||||
"models.add.group_name": "グループ名",
|
||||
"models.add.group_name.placeholder": "例:ChatGPT",
|
||||
"models.add.group_name.tooltip": "例:ChatGPT",
|
||||
"models.add.model_id": "モデルID",
|
||||
"models.add.model_id.placeholder": "必須 例:gpt-3.5-turbo",
|
||||
"models.add.model_id.tooltip": "例:gpt-3.5-turbo",
|
||||
"models.add.model_name": "モデル名",
|
||||
"models.add.model_name.placeholder": "例:GPT-3.5",
|
||||
"models.default_assistant_model": "デフォルトアシスタントモデル",
|
||||
"models.default_assistant_model_description": "新しいアシスタントを作成する際に使用されるモデル。アシスタントがモデルを設定していない場合、このモデルが使用されます",
|
||||
"models.empty": "モデルが見つかりません",
|
||||
"models.topic_naming_model": "トピック命名モデル",
|
||||
"models.topic_naming_model_description": "新しいトピックを自動的に命名する際に使用されるモデル",
|
||||
"models.translate_model": "翻訳モデル",
|
||||
"models.translate_model_description": "翻訳サービスに使用されるモデル",
|
||||
"models.translate_model_prompt_message": "翻訳モデルのプロンプトを入力してください",
|
||||
"models.translate_model_prompt_title": "翻訳モデルのプロンプト",
|
||||
"models.topic_naming_model_setting_title": "トピック命名モデルの設定",
|
||||
"models.enable_topic_naming": "トピックの自動命名",
|
||||
"models.topic_naming_prompt": "トピック命名プロンプト",
|
||||
"provider": {
|
||||
"add.name": "プロバイダー名",
|
||||
"add.name.placeholder": "例:OpenAI",
|
||||
"add.title": "プロバイダーを追加",
|
||||
"add.type": "プロバイダータイプ",
|
||||
"api.url.preview": "プレビュー: {{url}}",
|
||||
"api.url.reset": "リセット",
|
||||
"api.url.tip": "/で終わる場合、v1を無視します。#で終わる場合、入力されたアドレスを強制的に使用します",
|
||||
"api_host": "APIホスト",
|
||||
"api_key": "APIキー",
|
||||
"api_key.tip": "複数のキーはカンマで区切ります",
|
||||
"api_version": "APIバージョン",
|
||||
"check": "チェック",
|
||||
"check_all_keys": "すべてのキーをチェック",
|
||||
"check_multiple_keys": "複数のAPIキーをチェック",
|
||||
"delete.content": "このプロバイダーを削除してもよろしいですか?",
|
||||
"delete.title": "プロバイダーを削除",
|
||||
"docs_check": "チェック",
|
||||
"docs_more_details": "詳細を確認",
|
||||
"get_api_key": "APIキーを取得",
|
||||
"no_models": "API接続をチェックする前に、モデルを追加してください",
|
||||
"not_checked": "未チェック",
|
||||
"remove_duplicate_keys": "重複キーを削除",
|
||||
"remove_invalid_keys": "無効なキーを削除",
|
||||
"search_placeholder": "モデルIDまたは名前を検索",
|
||||
"title": "モデルプロバイダー"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
"custom": "カスタムプロキシ",
|
||||
"none": "プロキシを使用しない",
|
||||
"system": "システムプロキシ",
|
||||
"title": "プロキシモード"
|
||||
},
|
||||
"title": "プロキシ設定"
|
||||
},
|
||||
"proxy.title": "プロキシアドレス",
|
||||
"shortcuts": {
|
||||
"action": "操作",
|
||||
"key": "キー",
|
||||
"new_topic": "新しいトピック",
|
||||
"title": "ショートカット",
|
||||
"zoom_in": "ズームイン",
|
||||
"zoom_out": "ズームアウト",
|
||||
"zoom_reset": "ズームをリセット",
|
||||
"show_app": "アプリを表示",
|
||||
"reset_defaults": "デフォルトのショートカットをリセット",
|
||||
"reset_defaults_confirm": "すべてのショートカットをリセットしてもよろしいですか?",
|
||||
"press_shortcut": "ショートカットを押す",
|
||||
"alt_warning": "MacではOption + 文字をショートカットとして使用できません",
|
||||
"reset_to_default": "デフォルトにリセット",
|
||||
"clear_shortcut": "ショートカットをクリア",
|
||||
"toggle_show_assistants": "アシスタントの表示を切り替え",
|
||||
"toggle_show_topics": "トピックの表示を切り替え",
|
||||
"copy_last_message": "最後のメッセージをコピー"
|
||||
},
|
||||
"theme.auto": "自動",
|
||||
"theme.dark": "ダークテーマ",
|
||||
"theme.light": "ライトテーマ",
|
||||
"theme.title": "テーマ",
|
||||
"theme.window.style.opaque": "不透明ウィンドウ",
|
||||
"theme.window.style.title": "ウィンドウスタイル",
|
||||
"theme.window.style.transparent": "透明ウィンドウ",
|
||||
"title": "設定",
|
||||
"topic.position": "トピックの位置",
|
||||
"topic.position.left": "左",
|
||||
"topic.position.right": "右",
|
||||
"topic.show.time": "トピックの時間を表示",
|
||||
"tray.title": "システムトレイアイコンを有効にする"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "任意の言語",
|
||||
"button.translate": "翻訳",
|
||||
"confirm": {
|
||||
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
|
||||
"title": "翻訳確認"
|
||||
},
|
||||
"error.not_configured": "翻訳モデルが設定されていません",
|
||||
"error.failed": "翻訳に失敗しました",
|
||||
"input.placeholder": "翻訳するテキストを入力",
|
||||
"output.placeholder": "翻訳",
|
||||
"processing": "翻訳中...",
|
||||
"title": "翻訳",
|
||||
"close": "閉じる"
|
||||
},
|
||||
"tray": {
|
||||
"quit": "終了",
|
||||
"show_window": "ウィンドウを表示"
|
||||
},
|
||||
"words": {
|
||||
"knowledgeGraph": "ナレッジグラフ",
|
||||
"visualization": "可視化",
|
||||
"show_window": "ウィンドウを表示",
|
||||
"quit": "終了"
|
||||
},
|
||||
"knowledge_base": {
|
||||
"title": "ナレッジベース",
|
||||
"search": "ナレッジベースを検索",
|
||||
"empty": "ナレッジベースが見つかりません",
|
||||
"drag_file": "ファイルをここにドラッグ",
|
||||
"file_hint": "{{file_types}} 形式をサポート",
|
||||
"add": {
|
||||
"title": "ナレッジベースを追加"
|
||||
},
|
||||
"notes": "ノート",
|
||||
"notes_placeholder": "このナレッジベースの追加情報やコンテキストを入力...",
|
||||
"delete": "削除",
|
||||
"rename": "名前を変更",
|
||||
"urls": "URL",
|
||||
"add_url": "URLを追加",
|
||||
"url_placeholder": "URLを入力",
|
||||
"invalid_url": "無効なURL",
|
||||
"add_file": "ファイルを追加",
|
||||
"status": "状態",
|
||||
"index_all": "すべてをインデックス",
|
||||
"index_started": "インデックスを開始",
|
||||
"cancel_index": "インデックスをキャンセル",
|
||||
"index_cancelled": "インデックスがキャンセルされました",
|
||||
"status_new": "追加済み",
|
||||
"status_pending": "保留中",
|
||||
"status_processing": "処理中",
|
||||
"status_completed": "完了",
|
||||
"status_failed": "失敗",
|
||||
"url_added": "URLが追加されました",
|
||||
"search_placeholder": "検索するテキストを入力",
|
||||
"add_note": "ノートを追加",
|
||||
"no_bases": "ナレッジベースがありません",
|
||||
"clear_selection": "選択をクリア",
|
||||
"delete_confirm": "このナレッジベースを削除してもよろしいですか?",
|
||||
"sitemaps": "サイトマップ",
|
||||
"add_sitemap": "サイトマップを追加",
|
||||
"sitemap_placeholder": "サイトマップURLを入力",
|
||||
"directories": "ディレクトリ",
|
||||
"add_directory": "ディレクトリを追加",
|
||||
"directory_placeholder": "ディレクトリパスを入力"
|
||||
},
|
||||
"models": {
|
||||
"pinned": "固定済み",
|
||||
"search": "モデルを検索...",
|
||||
"stream_output": "ストリーム出力",
|
||||
"type": {
|
||||
"select": "モデルタイプを選択",
|
||||
"text": "テキスト",
|
||||
"vision": "画像",
|
||||
"embedding": "埋め込み"
|
||||
},
|
||||
"all": "すべて",
|
||||
"vision": "画像モデル",
|
||||
"websearch": "ウェブ検索モデル",
|
||||
"free": "無料モデル",
|
||||
"embedding": "埋め込みモデル",
|
||||
"embedding_model": "埋め込みモデル",
|
||||
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
|
||||
"dimensions": "{{dimensions}} 次元",
|
||||
"custom_parameters": "カスタムパラメータ",
|
||||
"add_parameter": "パラメータを追加",
|
||||
"parameter_name": "パラメータ名",
|
||||
"parameter_type": {
|
||||
"string": "テキスト",
|
||||
"number": "数値",
|
||||
"boolean": "真偽値"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "あなたは会話を得意とするアシスタントです。ユーザーの会話を10文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
import KeyvStorage from '@kangfenmao/keyv-storage'
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { APP_NAME } from './config/env'
|
||||
|
||||
function init() {
|
||||
localforage.config({
|
||||
driver: localforage.INDEXEDDB,
|
||||
name: 'CherryAI',
|
||||
version: 1.0,
|
||||
storeName: 'cherryai',
|
||||
description: `${APP_NAME} Storage`
|
||||
})
|
||||
|
||||
window.keyv = new KeyvStorage()
|
||||
window.keyv.init()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ let _agentGroups: Record<string, Agent[]> = {}
|
||||
|
||||
const AgentsPage: FC = () => {
|
||||
const [search, setSearch] = useState('')
|
||||
const [searchInput, setSearchInput] = useState('')
|
||||
|
||||
const agentGroups = useMemo(() => {
|
||||
if (Object.keys(_agentGroups).length === 0) {
|
||||
@@ -58,19 +59,21 @@ const AgentsPage: FC = () => {
|
||||
return groups
|
||||
}
|
||||
|
||||
Object.entries(agentGroups).forEach(([group, agents]) => {
|
||||
if (group === '精选') return
|
||||
const uniqueAgents = new Map<string, Agent>()
|
||||
|
||||
const filteredAgents = agents.filter(
|
||||
(agent) =>
|
||||
agent.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
agent.description?.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
if (filteredAgents.length > 0) {
|
||||
groups[group] = filteredAgents
|
||||
}
|
||||
Object.entries(agentGroups).forEach(([, agents]) => {
|
||||
agents.forEach((agent) => {
|
||||
if (
|
||||
(agent.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
agent.description?.toLowerCase().includes(search.toLowerCase())) &&
|
||||
!uniqueAgents.has(agent.name)
|
||||
) {
|
||||
uniqueAgents.set(agent.name, agent)
|
||||
}
|
||||
})
|
||||
})
|
||||
return groups
|
||||
|
||||
return { 搜索结果: Array.from(uniqueAgents.values()) }
|
||||
}, [agentGroups, search])
|
||||
|
||||
const getAgentName = (agent: Agent) => {
|
||||
@@ -152,6 +155,14 @@ const AgentsPage: FC = () => {
|
||||
})
|
||||
}, [filteredAgentGroups, getLocalizedGroupName, onAddAgentConfirm, search])
|
||||
|
||||
const handleSearch = () => {
|
||||
if (searchInput.trim() === '') {
|
||||
setSearch('')
|
||||
} else {
|
||||
setSearch(searchInput)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
@@ -164,18 +175,37 @@ const AgentsPage: FC = () => {
|
||||
size="small"
|
||||
variant="filled"
|
||||
allowClear
|
||||
suffix={<SearchOutlined />}
|
||||
value={search}
|
||||
onClear={() => setSearch('')}
|
||||
suffix={<SearchOutlined onClick={handleSearch} />}
|
||||
value={searchInput}
|
||||
maxLength={50}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
onPressEnter={handleSearch}
|
||||
/>
|
||||
<div style={{ width: 80 }} />
|
||||
</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<AssistantsContainer>
|
||||
{tabItems.length > 0 ? (
|
||||
<Tabs tabPosition="right" animated items={tabItems} />
|
||||
{Object.values(filteredAgentGroups).flat().length > 0 ? (
|
||||
search.trim() ? (
|
||||
<TabContent>
|
||||
<Row gutter={[20, 20]}>
|
||||
{Object.values(filteredAgentGroups)
|
||||
.flat()
|
||||
.map((agent, index, array) => (
|
||||
<Col span={array.length === 1 ? 12 : 6} key={index}>
|
||||
<AgentCard
|
||||
onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent as any))}
|
||||
agent={agent as any}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</TabContent>
|
||||
) : (
|
||||
<Tabs tabPosition="right" animated items={tabItems} $language={i18n.language} />
|
||||
)
|
||||
) : (
|
||||
<EmptyView>
|
||||
<Empty description={t('agents.search.no_results')} />
|
||||
@@ -234,7 +264,7 @@ const EmptyView = styled.div`
|
||||
color: var(--color-text-secondary);
|
||||
`
|
||||
|
||||
const Tabs = styled(TabsAntd)`
|
||||
const Tabs = styled(TabsAntd)<{ $language: string }>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row-reverse;
|
||||
@@ -242,8 +272,8 @@ const Tabs = styled(TabsAntd)`
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.ant-tabs-nav {
|
||||
min-width: 140px;
|
||||
max-width: 140px;
|
||||
min-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')};
|
||||
max-width: ${({ $language }) => ($language.startsWith('zh') ? '110px' : '140px')};
|
||||
}
|
||||
.ant-tabs-nav-list {
|
||||
padding: 10px 8px;
|
||||
@@ -259,6 +289,8 @@ const Tabs = styled(TabsAntd)`
|
||||
justify-content: left;
|
||||
padding: 7px 15px !important;
|
||||
border: 0.5px solid transparent;
|
||||
justify-content: ${({ $language }) => ($language.startsWith('zh') ? 'center' : 'flex-start')};
|
||||
user-select: none;
|
||||
.ant-tabs-tab-btn {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -4,6 +4,7 @@ export type GroupTranslations = {
|
||||
'zh-CN': string
|
||||
'zh-TW': string
|
||||
'ru-RU': string
|
||||
'ja-JP': string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,204 +13,238 @@ export const groupTranslations: GroupTranslations = {
|
||||
'en-US': 'My Agents',
|
||||
'zh-CN': '我的',
|
||||
'zh-TW': '我的',
|
||||
'ru-RU': 'Мои агенты'
|
||||
'ru-RU': 'Мои агенты',
|
||||
'ja-JP': '私のエージェント'
|
||||
},
|
||||
职业: {
|
||||
'en-US': 'Career',
|
||||
'zh-CN': '职业',
|
||||
'zh-TW': '職業',
|
||||
'ru-RU': 'Карьера'
|
||||
'ru-RU': 'Карьера',
|
||||
'ja-JP': 'キャリア'
|
||||
},
|
||||
商业: {
|
||||
'en-US': 'Business',
|
||||
'zh-CN': '商业',
|
||||
'zh-TW': '商業',
|
||||
'ru-RU': 'Бизнес'
|
||||
'ru-RU': 'Бизнес',
|
||||
'ja-JP': 'ビジネス'
|
||||
},
|
||||
工具: {
|
||||
'en-US': 'Tools',
|
||||
'zh-CN': '工具',
|
||||
'zh-TW': '工具',
|
||||
'ru-RU': 'Инструменты'
|
||||
'ru-RU': 'Инструменты',
|
||||
'ja-JP': 'ツール'
|
||||
},
|
||||
语言: {
|
||||
'en-US': 'Language',
|
||||
'zh-CN': '语言',
|
||||
'zh-TW': '語言',
|
||||
'ru-RU': 'Язык'
|
||||
'ru-RU': 'Язык',
|
||||
'ja-JP': '言語'
|
||||
},
|
||||
办公: {
|
||||
'en-US': 'Office',
|
||||
'zh-CN': '办公',
|
||||
'zh-TW': '辦公',
|
||||
'ru-RU': 'Офис'
|
||||
'ru-RU': 'Офис',
|
||||
'ja-JP': 'オフィス'
|
||||
},
|
||||
通用: {
|
||||
'en-US': 'General',
|
||||
'zh-CN': '通用',
|
||||
'zh-TW': '通用',
|
||||
'ru-RU': 'Общее'
|
||||
'ru-RU': 'Общее',
|
||||
'ja-JP': '一般'
|
||||
},
|
||||
写作: {
|
||||
'en-US': 'Writing',
|
||||
'zh-CN': '写作',
|
||||
'zh-TW': '寫作',
|
||||
'ru-RU': 'Письмо'
|
||||
'ru-RU': 'Письмо',
|
||||
'ja-JP': '書き込み'
|
||||
},
|
||||
精选: {
|
||||
'en-US': 'Featured',
|
||||
'zh-CN': '精选',
|
||||
'zh-TW': '精選',
|
||||
'ru-RU': 'Избранное'
|
||||
'ru-RU': 'Избранное',
|
||||
'ja-JP': '特集'
|
||||
},
|
||||
编程: {
|
||||
'en-US': 'Programming',
|
||||
'zh-CN': '编程',
|
||||
'zh-TW': '編程',
|
||||
'ru-RU': 'Программирование'
|
||||
'ru-RU': 'Программирование',
|
||||
'ja-JP': 'プログラミング'
|
||||
},
|
||||
情感: {
|
||||
'en-US': 'Emotion',
|
||||
'zh-CN': '情感',
|
||||
'zh-TW': '情感',
|
||||
'ru-RU': 'Эмоции'
|
||||
'ru-RU': 'Эмоции',
|
||||
'ja-JP': '感情'
|
||||
},
|
||||
教育: {
|
||||
'en-US': 'Education',
|
||||
'zh-CN': '教育',
|
||||
'zh-TW': '教育',
|
||||
'ru-RU': 'Образование'
|
||||
'ru-RU': 'Образование',
|
||||
'ja-JP': '教育'
|
||||
},
|
||||
创意: {
|
||||
'en-US': 'Creative',
|
||||
'zh-CN': '创意',
|
||||
'zh-TW': '創意',
|
||||
'ru-RU': 'Креатив'
|
||||
'ru-RU': 'Креатив',
|
||||
'ja-JP': 'クリエイティブ'
|
||||
},
|
||||
学术: {
|
||||
'en-US': 'Academic',
|
||||
'zh-CN': '学术',
|
||||
'zh-TW': '學術',
|
||||
'ru-RU': 'Академический'
|
||||
'ru-RU': 'Академический',
|
||||
'ja-JP': 'アカデミック'
|
||||
},
|
||||
设计: {
|
||||
'en-US': 'Design',
|
||||
'zh-CN': '设计',
|
||||
'zh-TW': '設計',
|
||||
'ru-RU': 'Дизайн'
|
||||
'ru-RU': 'Дизайн',
|
||||
'ja-JP': 'デザイン'
|
||||
},
|
||||
艺术: {
|
||||
'en-US': 'Art',
|
||||
'zh-CN': '艺术',
|
||||
'zh-TW': '藝術',
|
||||
'ru-RU': 'Искусство'
|
||||
'ru-RU': 'Искусство',
|
||||
'ja-JP': 'アート'
|
||||
},
|
||||
娱乐: {
|
||||
'en-US': 'Entertainment',
|
||||
'zh-CN': '娱乐',
|
||||
'zh-TW': '娛樂',
|
||||
'ru-RU': 'Развлечения'
|
||||
'ru-RU': 'Развлечения',
|
||||
'ja-JP': 'エンターテイメント'
|
||||
},
|
||||
生活: {
|
||||
'en-US': 'Life',
|
||||
'zh-CN': '生活',
|
||||
'zh-TW': '生活',
|
||||
'ru-RU': 'Жизнь'
|
||||
'ru-RU': 'Жизнь',
|
||||
'ja-JP': '生活'
|
||||
},
|
||||
医疗: {
|
||||
'en-US': 'Medical',
|
||||
'zh-CN': '医疗',
|
||||
'zh-TW': '醫療',
|
||||
'ru-RU': 'Медицина'
|
||||
'ru-RU': 'Медицина',
|
||||
'ja-JP': '医療'
|
||||
},
|
||||
游戏: {
|
||||
'en-US': 'Games',
|
||||
'zh-CN': '游戏',
|
||||
'zh-TW': '遊戲',
|
||||
'ru-RU': 'Игры'
|
||||
'ru-RU': 'Игры',
|
||||
'ja-JP': 'ゲーム'
|
||||
},
|
||||
翻译: {
|
||||
'en-US': 'Translation',
|
||||
'zh-CN': '翻译',
|
||||
'zh-TW': '翻譯',
|
||||
'ru-RU': 'Перевод'
|
||||
'ru-RU': 'Перевод',
|
||||
'ja-JP': '翻訳'
|
||||
},
|
||||
音乐: {
|
||||
'en-US': 'Music',
|
||||
'zh-CN': '音乐',
|
||||
'zh-TW': '音樂',
|
||||
'ru-RU': 'Музыка'
|
||||
'ru-RU': 'Музыка',
|
||||
'ja-JP': '音楽'
|
||||
},
|
||||
点评: {
|
||||
'en-US': 'Review',
|
||||
'zh-CN': '点评',
|
||||
'zh-TW': '點評',
|
||||
'ru-RU': 'Обзор'
|
||||
'ru-RU': 'Обзор',
|
||||
'ja-JP': 'レビュー'
|
||||
},
|
||||
文案: {
|
||||
'en-US': 'Copywriting',
|
||||
'zh-CN': '文案',
|
||||
'zh-TW': '文案',
|
||||
'ru-RU': 'Копирайтинг'
|
||||
'ru-RU': 'Копирайтинг',
|
||||
'ja-JP': 'コピーライティング'
|
||||
},
|
||||
百科: {
|
||||
'en-US': 'Encyclopedia',
|
||||
'zh-CN': '百科',
|
||||
'zh-TW': '百科',
|
||||
'ru-RU': 'Энциклопедия'
|
||||
'ru-RU': 'Энциклопедия',
|
||||
'ja-JP': '百科事典'
|
||||
},
|
||||
健康: {
|
||||
'en-US': 'Health',
|
||||
'zh-CN': '健康',
|
||||
'zh-TW': '健康',
|
||||
'ru-RU': 'Здоровье'
|
||||
'ru-RU': 'Здоровье',
|
||||
'ja-JP': '健康'
|
||||
},
|
||||
营销: {
|
||||
'en-US': 'Marketing',
|
||||
'zh-CN': '营销',
|
||||
'zh-TW': '營銷',
|
||||
'ru-RU': 'Маркетинг'
|
||||
'ru-RU': 'Маркетинг',
|
||||
'ja-JP': 'マーケティング'
|
||||
},
|
||||
科学: {
|
||||
'en-US': 'Science',
|
||||
'zh-CN': '科学',
|
||||
'zh-TW': '科學',
|
||||
'ru-RU': 'Наука'
|
||||
'ru-RU': 'Наука',
|
||||
'ja-JP': '科学'
|
||||
},
|
||||
分析: {
|
||||
'en-US': 'Analysis',
|
||||
'zh-CN': '分析',
|
||||
'zh-TW': '分析',
|
||||
'ru-RU': 'Анализ'
|
||||
'ru-RU': 'Анализ',
|
||||
'ja-JP': '分析'
|
||||
},
|
||||
法律: {
|
||||
'en-US': 'Legal',
|
||||
'zh-CN': '法律',
|
||||
'zh-TW': '法律',
|
||||
'ru-RU': 'Право'
|
||||
'ru-RU': 'Право',
|
||||
'ja-JP': '法律'
|
||||
},
|
||||
咨询: {
|
||||
'en-US': 'Consulting',
|
||||
'zh-CN': '咨询',
|
||||
'zh-TW': '諮詢',
|
||||
'ru-RU': 'Консалтинг'
|
||||
'ru-RU': 'Консалтинг',
|
||||
'ja-JP': 'コンサルティング'
|
||||
},
|
||||
金融: {
|
||||
'en-US': 'Finance',
|
||||
'zh-CN': '金融',
|
||||
'zh-TW': '金融',
|
||||
'ru-RU': 'Финансы'
|
||||
'ru-RU': 'Финансы',
|
||||
'ja-JP': '金融'
|
||||
},
|
||||
旅游: {
|
||||
'en-US': 'Travel',
|
||||
'zh-CN': '旅游',
|
||||
'zh-TW': '旅遊',
|
||||
'ru-RU': 'Путешествия'
|
||||
'ru-RU': 'Путешествия',
|
||||
'ja-JP': '旅行'
|
||||
},
|
||||
管理: {
|
||||
'en-US': 'Management',
|
||||
'zh-CN': '管理',
|
||||
'zh-TW': '管理',
|
||||
'ru-RU': 'Управление'
|
||||
'ru-RU': 'Управление',
|
||||
'ja-JP': '管理'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons'
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
||||
import { createAssistantFromAgent } from '@renderer/services/AssistantService'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Col } from 'antd'
|
||||
|
||||
@@ -5,16 +5,26 @@ import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
app: MinAppType
|
||||
onClick?: () => void
|
||||
size?: number
|
||||
}
|
||||
|
||||
const App: FC<Props> = ({ app }) => {
|
||||
const onClick = () => {
|
||||
const App: FC<Props> = ({ app, onClick, size = 60 }) => {
|
||||
const handleClick = () => {
|
||||
MinApp.start(app)
|
||||
onClick?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<Container onClick={onClick}>
|
||||
<AppIcon src={app.logo} style={{ border: app.bodered ? '0.5px solid var(--color-border)' : 'none' }} />
|
||||
<Container onClick={handleClick}>
|
||||
<AppIcon
|
||||
src={app.logo}
|
||||
style={{
|
||||
border: app.bodered ? '0.5px solid var(--color-border)' : 'none',
|
||||
width: `${size}px`,
|
||||
height: `${size}px`
|
||||
}}
|
||||
/>
|
||||
<AppTitle>{app.name}</AppTitle>
|
||||
</Container>
|
||||
)
|
||||
@@ -26,12 +36,10 @@ const Container = styled.div`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 65px;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const AppIcon = styled.img`
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
@@ -43,6 +51,7 @@ const AppTitle = styled.div`
|
||||
color: var(--color-text-soft);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
export default App
|
||||
|
||||
@@ -74,12 +74,12 @@ const ContentContainer = styled.div`
|
||||
|
||||
const AppsContainer = styled.div`
|
||||
display: flex;
|
||||
min-width: 900px;
|
||||
max-width: 900px;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
gap: 50px;
|
||||
min-width: 930px;
|
||||
max-width: 930px;
|
||||
max-height: 500px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, minmax(90px, 1fr));
|
||||
gap: 25px 25px;
|
||||
`
|
||||
|
||||
export default AppsPage
|
||||
|
||||