Compare commits
107 Commits
v0.8.22
...
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 | ||
|
|
a4d8e71916 | ||
|
|
39cf227e42 | ||
|
|
d2cad31db4 | ||
|
|
aa864f3876 | ||
|
|
77a8b23d76 |
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,20 +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,23 +1,48 @@
|
||||
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'),
|
||||
'@shared': resolve('packages/shared')
|
||||
}
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['@libsql/client']
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
plugins: [react()],
|
||||
plugins: [react(), ...visualizerPlugin('renderer')],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src'),
|
||||
@@ -25,7 +50,7 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: []
|
||||
exclude: ['chunk-QH6N6I7P.js', 'chunk-PB73W2YU.js']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
62
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "0.8.22",
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -95,18 +95,21 @@ export const ZOOM_SHORTCUTS = [
|
||||
key: 'zoom_in',
|
||||
shortcut: ['CommandOrControl', '='],
|
||||
editable: false,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
system: true
|
||||
},
|
||||
{
|
||||
key: 'zoom_out',
|
||||
shortcut: ['CommandOrControl', '-'],
|
||||
editable: false,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
system: true
|
||||
},
|
||||
{
|
||||
key: 'zoom_reset',
|
||||
shortcut: ['CommandOrControl', '0'],
|
||||
editable: false,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
system: true
|
||||
}
|
||||
]
|
||||
|
||||
202
resources/cherry-studio/releases.html
Normal file
@@ -0,0 +1,202 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Github Releases Timeline</title>
|
||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/typography@0.5.10/dist/typography.min.css"></script>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<div :class="isDark ? 'dark-bg' : 'bg'" class="min-h-screen">
|
||||
<div class="max-w-3xl mx-auto py-12 px-4">
|
||||
<h1 class="text-3xl font-bold mb-8" :class="isDark ? 'text-white' : 'text-gray-900'">Release Timeline</h1>
|
||||
|
||||
<!-- Loading状态 -->
|
||||
<div v-if="loading" class="text-center py-8">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-4"
|
||||
:class="isDark ? 'border-gray-700 border-t-blue-500' : 'border-gray-300 border-t-blue-500'"></div>
|
||||
</div>
|
||||
|
||||
<!-- Error 状态 -->
|
||||
<div v-else-if="error" class="text-red-500 text-center py-8">{{ error }}</div>
|
||||
|
||||
<!-- Release 列表 -->
|
||||
<div v-else class="space-y-8">
|
||||
<div v-for="release in releases" :key="release.id" class="relative pl-8"
|
||||
:class="isDark ? 'border-l-2 border-gray-700' : 'border-l-2 border-gray-200'">
|
||||
<div class="absolute -left-2 top-0 w-4 h-4 rounded-full bg-green-500"></div>
|
||||
<div class="rounded-lg shadow-sm p-6 transition-shadow"
|
||||
:class="isDark ? 'bg-black hover:shadow-md hover:shadow-black' : 'bg-white hover:shadow-md'">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold" :class="isDark ? 'text-white' : 'text-gray-900'">
|
||||
{{ release.name || release.tag_name }}
|
||||
</h2>
|
||||
<p class="text-sm mt-1" :class="isDark ? 'text-gray-400' : 'text-gray-500'">
|
||||
{{ formatDate(release.published_at) }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium"
|
||||
:class="isDark ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'">
|
||||
{{ release.tag_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="prose" :class="isDark ? 'text-gray-300 dark-prose' : 'text-gray-600'"
|
||||
v-html="renderMarkdown(release.body)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const md = window.markdownit({
|
||||
breaks: true,
|
||||
linkify: true
|
||||
})
|
||||
|
||||
const { createApp } = Vue
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
releases: [],
|
||||
loading: true,
|
||||
error: null,
|
||||
isDark: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchReleases() {
|
||||
try {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
const response = await fetch('https://api.github.com/repos/kangfenmao/cherry-studio/releases')
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch releases')
|
||||
}
|
||||
this.releases = await response.json()
|
||||
} catch (err) {
|
||||
this.error = 'Error loading releases: ' + err.message
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
},
|
||||
renderMarkdown(content) {
|
||||
if (!content) return ''
|
||||
return md.render(content)
|
||||
},
|
||||
initTheme() {
|
||||
// 从 URL 参数获取主题设置
|
||||
const url = new URL(window.location.href)
|
||||
const theme = url.searchParams.get('theme')
|
||||
this.isDark = theme === 'dark'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initTheme()
|
||||
this.fetchReleases()
|
||||
}
|
||||
}).mount('#app')
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 基础的 Markdown 样式 */
|
||||
.prose {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
font-size: 1.3em;
|
||||
margin: 0.8em 0;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
font-size: 1.1em;
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
margin-left: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
margin-left: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.2em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.dark .prose code {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
display: block;
|
||||
padding: 1em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
color: #3b82f6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dark .prose a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
border-left: 4px solid #e5e7eb;
|
||||
padding-left: 1em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.dark .prose blockquote {
|
||||
border-left-color: #374151;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.dark .prose {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.dark-bg {
|
||||
background-color: #151515;
|
||||
}
|
||||
|
||||
.bg {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
45
scripts/after-pack.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { Arch } = require('electron-builder')
|
||||
const { default: removeLocales } = require('./remove-locales')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
exports.default = async function (context) {
|
||||
await removeLocales(context)
|
||||
const platform = context.packager.platform.name
|
||||
const arch = context.arch
|
||||
|
||||
if (platform === 'mac') {
|
||||
const node_modules_path = path.join(
|
||||
context.appOutDir,
|
||||
'Cherry Studio.app',
|
||||
'Contents',
|
||||
'Resources',
|
||||
'app.asar.unpacked',
|
||||
'node_modules'
|
||||
)
|
||||
|
||||
removeDifferentArchNodeFiles(node_modules_path, '@libsql', arch === Arch.arm64 ? ['darwin-arm64'] : ['darwin-x64'])
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
|
||||
const _arch = arch === Arch.arm64 ? ['linux-arm64-gnu', 'linux-arm64-musl'] : ['linux-x64-gnu', 'linux-x64-musl']
|
||||
removeDifferentArchNodeFiles(node_modules_path, '@libsql', _arch)
|
||||
}
|
||||
|
||||
if (platform === 'windows') {
|
||||
const node_modules_path = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules')
|
||||
removeDifferentArchNodeFiles(node_modules_path, '@libsql', ['win32-x64-msvc'])
|
||||
}
|
||||
}
|
||||
|
||||
function removeDifferentArchNodeFiles(nodeModulesPath, packageName, arch) {
|
||||
const modulePath = path.join(nodeModulesPath, packageName)
|
||||
const dirs = fs.readdirSync(modulePath)
|
||||
dirs
|
||||
.filter((dir) => !arch.includes(dir))
|
||||
.forEach((dir) => {
|
||||
fs.rmSync(path.join(modulePath, dir), { recursive: true, force: true })
|
||||
console.log(`Removed dir: ${dir}`, arch)
|
||||
})
|
||||
}
|
||||
40
scripts/build-npm.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { downloadNpmPackage } = require('./utils')
|
||||
|
||||
async function downloadNpm(platform) {
|
||||
if (!platform || platform === 'mac') {
|
||||
downloadNpmPackage(
|
||||
'@libsql/darwin-arm64',
|
||||
'https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.4.7.tgz'
|
||||
)
|
||||
downloadNpmPackage('@libsql/darwin-x64', 'https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.4.7.tgz')
|
||||
}
|
||||
|
||||
if (!platform || platform === 'linux') {
|
||||
downloadNpmPackage(
|
||||
'@libsql/linux-arm64-gnu',
|
||||
'https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.4.7.tgz'
|
||||
)
|
||||
downloadNpmPackage(
|
||||
'@libsql/linux-arm64-musl',
|
||||
'https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.4.7.tgz'
|
||||
)
|
||||
downloadNpmPackage(
|
||||
'@libsql/linux-x64-gnu',
|
||||
'https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.4.7.tgz'
|
||||
)
|
||||
downloadNpmPackage(
|
||||
'@libsql/linux-x64-musl',
|
||||
'https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.4.7.tgz'
|
||||
)
|
||||
}
|
||||
|
||||
if (!platform || platform === 'windows') {
|
||||
downloadNpmPackage(
|
||||
'@libsql/win32-x64-msvc',
|
||||
'https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.4.7.tgz'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const platformArg = process.argv[2]
|
||||
downloadNpm(platformArg)
|
||||
58
scripts/remove-locales.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
exports.default = async function (context) {
|
||||
const platform = context.packager.platform.name
|
||||
|
||||
// 根据平台确定 locales 目录位置
|
||||
let resourceDirs = []
|
||||
if (platform === 'mac') {
|
||||
// macOS 的语言文件位置
|
||||
resourceDirs = [
|
||||
path.join(context.appOutDir, 'Cherry Studio.app', 'Contents', 'Resources'),
|
||||
path.join(
|
||||
context.appOutDir,
|
||||
'Cherry Studio.app',
|
||||
'Contents',
|
||||
'Frameworks',
|
||||
'Electron Framework.framework',
|
||||
'Resources'
|
||||
)
|
||||
]
|
||||
} else {
|
||||
// Windows 和 Linux 的语言文件位置
|
||||
resourceDirs = [path.join(context.appOutDir, 'locales')]
|
||||
}
|
||||
|
||||
// 处理每个资源目录
|
||||
for (const resourceDir of resourceDirs) {
|
||||
if (!fs.existsSync(resourceDir)) {
|
||||
console.log(`Resource directory not found: ${resourceDir}, skipping...`)
|
||||
continue
|
||||
}
|
||||
|
||||
// 读取所有文件和目录
|
||||
const items = fs.readdirSync(resourceDir)
|
||||
|
||||
// 遍历并删除不需要的语言文件
|
||||
for (const item of items) {
|
||||
if (platform === 'mac') {
|
||||
// 在 macOS 上检查 .lproj 目录
|
||||
if (item.endsWith('.lproj') && !item.match(/^(en|zh|ru)/)) {
|
||||
const dirPath = path.join(resourceDir, item)
|
||||
fs.rmSync(dirPath, { recursive: true, force: true })
|
||||
console.log(`Removed locale directory: ${item} from ${resourceDir}`)
|
||||
}
|
||||
} else {
|
||||
// 其他平台处理 .pak 文件
|
||||
if (!item.match(/^(en|zh|ru)/)) {
|
||||
const filePath = path.join(resourceDir, item)
|
||||
fs.unlinkSync(filePath)
|
||||
console.log(`Removed locale file: ${item} from ${resourceDir}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Locale cleanup completed!')
|
||||
}
|
||||
58
scripts/replace-spaces.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// replaceSpaces.js
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const directory = 'dist'
|
||||
|
||||
// 处理文件名中的空格
|
||||
function replaceFileNames() {
|
||||
fs.readdir(directory, (err, files) => {
|
||||
if (err) throw err
|
||||
|
||||
files.forEach((file) => {
|
||||
const oldPath = path.join(directory, file)
|
||||
const newPath = path.join(directory, file.replace(/ /g, '-'))
|
||||
|
||||
fs.stat(oldPath, (err, stats) => {
|
||||
if (err) throw err
|
||||
|
||||
if (stats.isFile() && oldPath !== newPath) {
|
||||
fs.rename(oldPath, newPath, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`Renamed: ${oldPath} -> ${newPath}`)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function replaceYmlContent() {
|
||||
fs.readdir(directory, (err, files) => {
|
||||
if (err) throw err
|
||||
|
||||
files.forEach((file) => {
|
||||
if (path.extname(file).toLowerCase() === '.yml') {
|
||||
const filePath = path.join(directory, file)
|
||||
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) throw err
|
||||
|
||||
// 替换内容
|
||||
const newContent = data.replace(/Cherry Studio-/g, 'Cherry-Studio-')
|
||||
|
||||
// 写回文件
|
||||
fs.writeFile(filePath, newContent, 'utf8', (err) => {
|
||||
if (err) throw err
|
||||
console.log(`Updated content in: ${filePath}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 执行两个操作
|
||||
replaceFileNames()
|
||||
replaceYmlContent()
|
||||
@@ -1,26 +0,0 @@
|
||||
// replaceSpaces.js
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const directory = 'dist'
|
||||
|
||||
fs.readdir(directory, (err, files) => {
|
||||
if (err) throw err
|
||||
|
||||
files.forEach((file) => {
|
||||
const oldPath = path.join(directory, file)
|
||||
const newPath = path.join(directory, file.replace(/ /g, '-'))
|
||||
|
||||
fs.stat(oldPath, (err, stats) => {
|
||||
if (err) throw err
|
||||
|
||||
if (stats.isFile() && oldPath !== newPath) {
|
||||
fs.rename(oldPath, newPath, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`Renamed: ${oldPath} -> ${newPath}`)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
39
scripts/utils.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
function downloadNpmPackage(packageName, url) {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'npm-download-'))
|
||||
|
||||
const targetDir = path.join('./node_modules/', packageName)
|
||||
const filename = packageName.replace('/', '-') + '.tgz'
|
||||
|
||||
// Skip if directory already exists
|
||||
if (fs.existsSync(targetDir)) {
|
||||
console.log(`${targetDir} already exists, skipping download...`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Downloading ${packageName}...`, url)
|
||||
const { execSync } = require('child_process')
|
||||
execSync(`curl --fail -o ${filename} ${url}`)
|
||||
|
||||
console.log(`Extracting ${filename}...`)
|
||||
execSync(`tar -xvf ${filename}`)
|
||||
execSync(`rm -rf ${filename}`)
|
||||
execSync(`mv package ${targetDir}`)
|
||||
} catch (error) {
|
||||
console.error(`Error processing ${packageName}: ${error.message}`)
|
||||
if (fs.existsSync(filename)) {
|
||||
fs.unlinkSync(filename)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
fs.rmSync(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
downloadNpmPackage
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import vm from 'node:vm'
|
||||
|
||||
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,7 @@ 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'
|
||||
@@ -100,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)
|
||||
@@ -144,4 +148,18 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
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,15 +21,8 @@ export default class AppUpdater {
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
||||
autoUpdater.logger?.info('检测到新版本,开始自动下载')
|
||||
logger.info('检测到新版本', releaseInfo)
|
||||
mainWindow.webContents.send('update-available', releaseInfo)
|
||||
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: '正在下载新版本',
|
||||
message: `新版本 ${releaseInfo.version}`,
|
||||
detail: this.formatReleaseNotes(releaseInfo.releaseNotes)
|
||||
})
|
||||
})
|
||||
|
||||
// 检测到不需要更新时
|
||||
@@ -36,18 +32,20 @@ export default class AppUpdater {
|
||||
|
||||
// 更新下载进度
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
logger.info('下载进度', progress)
|
||||
mainWindow.webContents.send('download-progress', progress)
|
||||
})
|
||||
|
||||
// 当需要更新的内容下载完成后
|
||||
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||||
mainWindow.webContents.send('update-downloaded')
|
||||
|
||||
logger.info('下载完成,询问用户是否更新', releaseInfo)
|
||||
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: 'info',
|
||||
title: '安装更新',
|
||||
icon,
|
||||
message: `新版本 ${releaseInfo.version} 已准备就绪`,
|
||||
detail: this.formatReleaseNotes(releaseInfo.releaseNotes),
|
||||
buttons: ['稍后安装', '立即安装'],
|
||||
@@ -58,6 +56,8 @@ export default class AppUpdater {
|
||||
if (response === 1) {
|
||||
app.isQuitting = true
|
||||
setImmediate(() => autoUpdater.quitAndInstall())
|
||||
} else {
|
||||
mainWindow.webContents.send('update-downloaded-cancelled')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -77,7 +77,10 @@ export class ConfigManager {
|
||||
}
|
||||
|
||||
setShortcuts(shortcuts: Shortcut[]) {
|
||||
this.store.set('shortcuts', shortcuts)
|
||||
this.store.set(
|
||||
'shortcuts',
|
||||
shortcuts.filter((shortcut) => shortcut.system)
|
||||
)
|
||||
this.notifySubscribers('shortcuts', shortcuts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
import { Shortcut } from '@types'
|
||||
import { BrowserWindow, globalShortcut } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
|
||||
import { configManager } from './ConfigManager'
|
||||
|
||||
@@ -55,40 +56,44 @@ export function registerShortcuts(window: BrowserWindow) {
|
||||
if (!shortcuts) return
|
||||
|
||||
shortcuts.forEach((shortcut) => {
|
||||
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
|
||||
try {
|
||||
if (shortcut.shortcut.length === 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcut.enabled) {
|
||||
globalShortcut.register(accelerator, () => handler(window))
|
||||
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}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -96,11 +101,15 @@ export function registerShortcuts(window: BrowserWindow) {
|
||||
const unregister = () => {
|
||||
if (window.isDestroyed()) return
|
||||
|
||||
globalShortcut.unregisterAll()
|
||||
try {
|
||||
globalShortcut.unregisterAll()
|
||||
|
||||
if (showAppAccelerator) {
|
||||
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
|
||||
handler && globalShortcut.register(showAppAccelerator, () => handler(window))
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +122,10 @@ export function registerShortcuts(window: BrowserWindow) {
|
||||
}
|
||||
|
||||
export function unregisterAllShortcuts() {
|
||||
showAppAccelerator = null
|
||||
globalShortcut.unregisterAll()
|
||||
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' }
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
26
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,
|
||||
@@ -57,6 +60,25 @@ declare global {
|
||||
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 { Shortcut, 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'),
|
||||
@@ -50,6 +51,28 @@ const api = {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
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
|
||||
|
||||
@@ -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)
|
||||
@@ -73,12 +75,17 @@ 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() {
|
||||
|
||||
@@ -53,7 +53,8 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
||||
const message = getUserMessage({
|
||||
assistant,
|
||||
topic: getDefaultTopic('default'),
|
||||
type: 'text'
|
||||
type: 'text',
|
||||
content: text
|
||||
})
|
||||
|
||||
const translatedText = await fetchTranslate({ message, assistant })
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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 =
|
||||
'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 style="padding: 10px"><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 style="padding: 10px"><code>${code}</code></pre>`
|
||||
return `<pre style="padding: 10px"><code>${escapedCode}</code></pre>`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileType, Topic } from '@renderer/types'
|
||||
import { FileType, KnowledgeItem, Topic } from '@renderer/types'
|
||||
import { Dexie, type EntityTable } from 'dexie'
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
@@ -6,6 +6,7 @@ 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({
|
||||
@@ -18,4 +19,11 @@ db.version(2).stores({
|
||||
settings: '&id, value'
|
||||
})
|
||||
|
||||
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
src/renderer/src/env.d.ts
vendored
@@ -19,5 +19,6 @@ declare global {
|
||||
modal: HookAPI
|
||||
keyv: KeyvStorage
|
||||
mermaid: any
|
||||
store: any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
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 { updateShortcut } from '@renderer/store/shortcuts'
|
||||
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 { useShortcuts } from './useShortcuts'
|
||||
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'))
|
||||
const { shortcuts } = useShortcuts()
|
||||
|
||||
useUpdateHandler()
|
||||
|
||||
useEffect(() => {
|
||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||
@@ -31,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') {
|
||||
@@ -74,13 +85,24 @@ export function useAppInit() {
|
||||
}, [dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (isWindows) {
|
||||
shortcuts.forEach((shortcut) => {
|
||||
if (shortcut.shortcut[0] === 'Command') {
|
||||
shortcut.shortcut[0] = 'Ctrl'
|
||||
dispatch(updateShortcut(shortcut))
|
||||
}
|
||||
})
|
||||
webdavAutoSync ? startAutoSync() : stopAutoSync()
|
||||
}, [webdavAutoSync, webdavSyncInterval])
|
||||
|
||||
useEffect(() => {
|
||||
import('@renderer/queue/KnowledgeQueue')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const oldCustomCss = document.getElementById('user-defined-custom-css')
|
||||
if (oldCustomCss) {
|
||||
oldCustomCss.remove()
|
||||
}
|
||||
}, [dispatch, shortcuts])
|
||||
|
||||
if (customCss) {
|
||||
const style = document.createElement('style')
|
||||
style.id = 'user-defined-custom-css'
|
||||
style.textContent = customCss
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}, [customCss])
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { db } from '@renderer/databases'
|
||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||
import { 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
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isMac, isWindows } from '@renderer/config/constant'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { useCallback } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
@@ -38,7 +39,7 @@ export const useShortcut = (
|
||||
const shortcutConfig = shortcuts.find((s) => s.key === shortcutKey)
|
||||
|
||||
useHotkeys(
|
||||
shortcutConfig?.enabled ? formatShortcut(shortcutConfig.shortcut) : '',
|
||||
shortcutConfig?.enabled ? formatShortcut(shortcutConfig.shortcut) : 'none',
|
||||
(e) => {
|
||||
if (options.preventDefault) {
|
||||
e.preventDefault()
|
||||
@@ -49,7 +50,8 @@ export const useShortcut = (
|
||||
},
|
||||
{
|
||||
enableOnFormTags: options.enableOnFormTags,
|
||||
description: options.description || shortcutConfig?.key
|
||||
description: options.description || shortcutConfig?.key,
|
||||
enabled: !!shortcutConfig?.enabled
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -58,3 +60,31 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"input.estimated_tokens.tip": "Estimated tokens",
|
||||
"input.expand": "Expand",
|
||||
"input.new.context": "Clear Context",
|
||||
"input.new_topic": "New Topic {{Command}}+N",
|
||||
"input.new_topic": "New Topic {{Command}}",
|
||||
"input.pause": "Pause",
|
||||
"input.placeholder": "Type your message here...",
|
||||
"input.send": "Send",
|
||||
@@ -80,21 +80,26 @@
|
||||
"input.topics": " Topics ",
|
||||
"input.translate": "Translate to English",
|
||||
"input.upload": "Upload image or document file",
|
||||
"input.web_search": "Enable web search",
|
||||
"input.knowledge_base": "Knowledge Base",
|
||||
"message.new.branch": "New Branch",
|
||||
"message.new.branch.created": "New Branch Created",
|
||||
"message.regenerate.model": "Switch Model",
|
||||
"message.new.context": "New Context",
|
||||
"save": "Save",
|
||||
"settings.code_collapsible": "Code block collapsible",
|
||||
"settings.context_count": "Context",
|
||||
"settings.context_count.tip": "The number of previous messages to keep in the context.",
|
||||
"settings.max": "Max",
|
||||
"settings.max_tokens": "Enable Max Tokens Limit",
|
||||
"settings.max_tokens": "Enable max tokens limit",
|
||||
"settings.max_tokens.tip": "The maximum number of tokens the model can generate. Normal chat suggests 500-800. Short text generation suggests 800-2000. Code generation suggests 2000-3600. Long text generation suggests above 4000.",
|
||||
"settings.reset": "Reset",
|
||||
"settings.set_as_default": "Apply to default assistant",
|
||||
"settings.show_line_numbers": "Show Line Numbers in Code",
|
||||
"settings.show_line_numbers": "Show line numbers in code",
|
||||
"settings.temperature": "Temperature",
|
||||
"settings.temperature.tip": "Lower values make the model more creative and unpredictable, while higher values make it more deterministic and precise.",
|
||||
"settings.top_p": "Top-P",
|
||||
"settings.top_p.tip": "Default value is 1, the smaller the value, the less variety in the answers, the easier to understand, the larger the value, the larger the range of the AI's vocabulary, the more diverse",
|
||||
"suggestions.title": "Suggested Questions",
|
||||
"topics.auto_rename": "Auto Rename",
|
||||
"topics.clear.title": "Clear Messages",
|
||||
@@ -107,7 +112,8 @@
|
||||
"topics.list": "Topic List",
|
||||
"topics.move_to": "Move to",
|
||||
"topics.title": "Topics",
|
||||
"translate": "Translate"
|
||||
"translate": "Translate",
|
||||
"resend": "Resend"
|
||||
},
|
||||
"common": {
|
||||
"and": "and",
|
||||
@@ -218,17 +224,19 @@
|
||||
"assistant.added.content": "Assistant added successfully",
|
||||
"backup.failed": "Backup failed",
|
||||
"backup.success": "Backup successful",
|
||||
"backup.start.success": "Backup started",
|
||||
"chat.completion.paused": "Chat completion paused",
|
||||
"copied": "Copied!",
|
||||
"error.enter.api.host": "Please enter your API host first",
|
||||
"error.enter.api.key": "Please enter your API key first",
|
||||
"error.enter.model": "Please select a model first",
|
||||
"error.enter.name": "Please enter the name of the knowledge base",
|
||||
"error.invalid.proxy.url": "Invalid proxy URL",
|
||||
"error.invalid.webdav": "Invalid WebDAV settings",
|
||||
"message.code_style": "Code style",
|
||||
"message.delete.content": "Are you sure you want to delete this message?",
|
||||
"message.delete.title": "Delete Message",
|
||||
"message.style": "Message Style",
|
||||
"message.style": "Message style",
|
||||
"message.style.bubble": "Bubble",
|
||||
"message.style.plain": "Plain",
|
||||
"reset.confirm.content": "Are you sure you want to clear all data?",
|
||||
@@ -241,21 +249,13 @@
|
||||
"upgrade.success.button": "Restart",
|
||||
"upgrade.success.content": "Please restart the application to complete the upgrade",
|
||||
"upgrade.success.title": "Upgrade successfully",
|
||||
"regenerate.confirm": "Regenerating will replace current message"
|
||||
"regenerate.confirm": "Regenerating will replace current message",
|
||||
"copy.success": "Copied!",
|
||||
"error.get_embedding_dimensions": "Failed to get embedding dimensions"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "MinApp"
|
||||
},
|
||||
"model": {
|
||||
"pinned": "Pinned",
|
||||
"search": "Search models...",
|
||||
"stream_output": "Stream Output",
|
||||
"type": {
|
||||
"select": "Select Model Types",
|
||||
"text": "Text",
|
||||
"vision": "Vision"
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.",
|
||||
"keep_alive_time.placeholder": "Minutes",
|
||||
@@ -316,6 +316,7 @@
|
||||
"settings": {
|
||||
"about": "About & Feedback",
|
||||
"about.checkUpdate": "Check Update",
|
||||
"about.checkUpdate.available": "Update",
|
||||
"about.checkingUpdate": "Checking for updates...",
|
||||
"about.contact.button": "Email",
|
||||
"about.contact.title": "Contact",
|
||||
@@ -328,10 +329,12 @@
|
||||
"about.releases.button": "Releases",
|
||||
"about.releases.title": "Release Notes",
|
||||
"about.title": "About",
|
||||
"about.updateAvailable": "Found new version {{version}}",
|
||||
"about.updateError": "Update error",
|
||||
"about.updateNotAvailable": "You are using the latest version",
|
||||
"about.website.button": "Website",
|
||||
"about.website.title": "Official Website",
|
||||
"about.social.title": "Social Accounts",
|
||||
"advanced.auto_switch_to_topics": "Auto switch to topic",
|
||||
"advanced.title": "Advanced Settings",
|
||||
"assistant": "Default Assistant",
|
||||
@@ -355,12 +358,14 @@
|
||||
"webdav.password": "WebDAV Password",
|
||||
"webdav.path": "WebDAV Path",
|
||||
"webdav.path.placeholder": "/backup",
|
||||
"webdav.autoSync": "Auto Sync",
|
||||
"webdav.minutes": "Minutes",
|
||||
"webdav.restore.button": "Restore from WebDAV",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV User"
|
||||
},
|
||||
"display.title": "Display Settings",
|
||||
"font_size.title": "Message Font Size",
|
||||
"font_size.title": "Message font size",
|
||||
"general": "General Settings",
|
||||
"general.backup.button": "Backup",
|
||||
"general.backup.title": "Data Backup and Recovery",
|
||||
@@ -372,17 +377,26 @@
|
||||
"general.user_name": "User Name",
|
||||
"general.user_name.placeholder": "Enter your name",
|
||||
"general.view_webdav_settings": "View WebDAV settings",
|
||||
"general.display.title": "Display Settings",
|
||||
"display.sidebar.minapp.icon": "Show MinApp icon",
|
||||
"display.sidebar.files.icon": "Show Files icon",
|
||||
"display.sidebar.title": "Sidebar Settings",
|
||||
"display.topic.title": "Topic Settings",
|
||||
"display.custom.css": "Custom CSS",
|
||||
"display.custom.css.placeholder": "/* Put custom CSS here */",
|
||||
"input.auto_translate_with_space": "Quickly translate with 3 spaces",
|
||||
"messages.divider": "Show divider between messages",
|
||||
"messages.input.paste_long_text_as_file": "Paste long text as file",
|
||||
"messages.input.send_shortcuts": "Send shortcuts",
|
||||
"messages.input.show_estimated_tokens": "Show estimated tokens",
|
||||
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
|
||||
"messages.input.title": "Input Settings",
|
||||
"messages.markdown_rendering_input_message": "Markdown render input msg",
|
||||
"messages.math_engine": "Math render engine",
|
||||
"messages.model.title": "Model Settings",
|
||||
"messages.title": "Message Settings",
|
||||
"messages.use_serif_font": "Use serif font",
|
||||
"messages.input.paste_long_text_threshold": "Paste long text length",
|
||||
"model": "Default Model",
|
||||
"models.add.add_model": "Add Model",
|
||||
"models.add.group_name": "Group Name",
|
||||
@@ -404,6 +418,7 @@
|
||||
"models.translate_model_prompt_title": "Translate Model Prompt",
|
||||
"models.topic_naming_model_setting_title": "Topic Naming Model Settings",
|
||||
"models.enable_topic_naming": "Topic Auto Naming",
|
||||
"models.topic_naming_prompt": "Topic Naming Prompt",
|
||||
"provider": {
|
||||
"add.name": "Provider Name",
|
||||
"add.name.placeholder": "Example: OpenAI",
|
||||
@@ -465,9 +480,12 @@
|
||||
"reset_defaults": "Reset Defaults",
|
||||
"reset_defaults_confirm": "Are you sure you want to reset all shortcuts?",
|
||||
"press_shortcut": "Press Shortcut",
|
||||
"alt_warning": "Mac does not support the Alt key",
|
||||
"alt_warning": "Mac does not support Option + letters as shortcuts",
|
||||
"reset_to_default": "Reset to Default",
|
||||
"clear_shortcut": "Clear Shortcut"
|
||||
"clear_shortcut": "Clear Shortcut",
|
||||
"toggle_show_assistants": "Toggle Assistants",
|
||||
"toggle_show_topics": "Toggle Topics",
|
||||
"copy_last_message": "Copy Last Message"
|
||||
},
|
||||
"theme.auto": "Auto",
|
||||
"theme.dark": "Dark",
|
||||
@@ -477,10 +495,10 @@
|
||||
"theme.window.style.title": "Window Style",
|
||||
"theme.window.style.transparent": "Transparent Window",
|
||||
"title": "Settings",
|
||||
"topic.position": "Topic Position",
|
||||
"topic.position": "Topic position",
|
||||
"topic.position.left": "Left",
|
||||
"topic.position.right": "Right",
|
||||
"topic.show.time": "Show Topic Time",
|
||||
"topic.show.time": "Show topic time",
|
||||
"tray.title": "Enable System Tray Icon"
|
||||
},
|
||||
"translate": {
|
||||
@@ -504,7 +522,83 @@
|
||||
},
|
||||
"words": {
|
||||
"knowledgeGraph": "Knowledge Graph",
|
||||
"visualization": "Visualization"
|
||||
"visualization": "Visualization",
|
||||
"show_window": "Show Window",
|
||||
"quit": "Quit"
|
||||
},
|
||||
"knowledge_base": {
|
||||
"title": "Knowledge Base",
|
||||
"search": "Search knowledge base",
|
||||
"empty": "No knowledge base found",
|
||||
"drag_file": "Drag file here",
|
||||
"file_hint": "Support {{file_types}}",
|
||||
"add": {
|
||||
"title": "Add Knowledge Base"
|
||||
},
|
||||
"notes": "Notes",
|
||||
"notes_placeholder": "Enter additional information or context for this knowledge base...",
|
||||
"delete": "Delete",
|
||||
"rename": "Rename",
|
||||
"urls": "URLs",
|
||||
"add_url": "Add URL",
|
||||
"url_placeholder": "Enter URL",
|
||||
"invalid_url": "Invalid URL",
|
||||
"add_file": "Add File",
|
||||
"status": "Status",
|
||||
"index_all": "Index All",
|
||||
"index_started": "Indexing started",
|
||||
"cancel_index": "Cancel Indexing",
|
||||
"index_cancelled": "Indexing cancelled",
|
||||
"status_new": "Added",
|
||||
"status_pending": "Pending",
|
||||
"status_processing": "Processing",
|
||||
"status_completed": "Completed",
|
||||
"status_failed": "Failed",
|
||||
"url_added": "URL added",
|
||||
"search_placeholder": "Enter text to search",
|
||||
"add_note": "Add Note",
|
||||
"no_bases": "No knowledge bases available",
|
||||
"clear_selection": "Clear selection",
|
||||
"delete_confirm": "Are you sure you want to delete this knowledge base?",
|
||||
"sitemaps": "Websites",
|
||||
"add_sitemap": "Website Map",
|
||||
"sitemap_placeholder": "Enter Website Map URL",
|
||||
"directories": "Directories",
|
||||
"add_directory": "Add Directory",
|
||||
"directory_placeholder": "Enter Directory Path",
|
||||
"model_info": "Model Info",
|
||||
"not_support": "Knowledge base database engine updated, the knowledge base will no longer be supported, please create a new knowledge base",
|
||||
"source": "Source"
|
||||
},
|
||||
"models": {
|
||||
"pinned": "Pinned",
|
||||
"search": "Search models...",
|
||||
"stream_output": "Stream output",
|
||||
"type": {
|
||||
"select": "Select Model Types",
|
||||
"text": "Text",
|
||||
"vision": "Vision",
|
||||
"embedding": "Embedding"
|
||||
},
|
||||
"all": "All",
|
||||
"vision": "Vision",
|
||||
"websearch": "WebSearch",
|
||||
"free": "Free",
|
||||
"embedding": "Embedding",
|
||||
"embedding_model": "Embedding Model",
|
||||
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage",
|
||||
"dimensions": "Dimensions {{dimensions}}",
|
||||
"custom_parameters": "Custom Parameters",
|
||||
"add_parameter": "Add Parameter",
|
||||
"parameter_name": "Parameter Name",
|
||||
"parameter_type": {
|
||||
"string": "Text",
|
||||
"number": "Number",
|
||||
"boolean": "Boolean"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "You are an assistant who is good at conversation. You need to summarize the user's conversation into a title of 10 characters or less, ensuring it matches the user's primary language without using punctuation or other special symbols."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
"input.estimated_tokens.tip": "Затраты токенов",
|
||||
"input.expand": "Развернуть",
|
||||
"input.new.context": "Очистить контекст",
|
||||
"input.new_topic": "Новый топик {{Command}}+N",
|
||||
"input.new_topic": "Новый топик {{Command}}",
|
||||
"input.pause": "Остановить",
|
||||
"input.placeholder": "Введите ваше сообщение здесь...",
|
||||
"input.send": "Отправить",
|
||||
@@ -80,8 +80,11 @@
|
||||
"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": "Блок кода свернут",
|
||||
@@ -95,6 +98,8 @@
|
||||
"settings.show_line_numbers": "Показать номера строк в коде",
|
||||
"settings.temperature": "Температура",
|
||||
"settings.temperature.tip": "Меньшие значения делают модель более креативной и непредсказуемой, в то время как большие значения делают её более детерминированной и точной.",
|
||||
"settings.top_p": "Top-P",
|
||||
"settings.top_p.tip": "Значение по умолчанию 1, чем меньше значение, тем меньше вариативности в ответах, тем проще понять, чем больше значение, тем больше вариативности в ответах, тем больше разнообразие",
|
||||
"suggestions.title": "Предложенные вопросы",
|
||||
"topics.auto_rename": "Автопереименование",
|
||||
"topics.clear.title": "Очистить сообщения",
|
||||
@@ -107,7 +112,8 @@
|
||||
"topics.list": "Список топиков",
|
||||
"topics.move_to": "Переместить в",
|
||||
"topics.title": "Топики",
|
||||
"translate": "Перевести"
|
||||
"translate": "Перевести",
|
||||
"resend": "Переотправить"
|
||||
},
|
||||
"common": {
|
||||
"and": "и",
|
||||
@@ -218,11 +224,13 @@
|
||||
"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.enter.name": "Пожалуйста, введите название базы знаний",
|
||||
"error.invalid.proxy.url": "Неверный URL прокси",
|
||||
"error.invalid.webdav": "Неверные настройки WebDAV",
|
||||
"message.code_style": "Стиль кода",
|
||||
@@ -241,21 +249,13 @@
|
||||
"upgrade.success.button": "Перезапустить",
|
||||
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
|
||||
"upgrade.success.title": "Обновление успешно",
|
||||
"regenerate.confirm": "Перегенерация заменит текущее сообщение"
|
||||
"regenerate.confirm": "Перегенерация заменит текущее сообщение",
|
||||
"copy.success": "Скопировано!",
|
||||
"error.get_embedding_dimensions": "Не удалось получить размерность встраивания"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "Встроенные приложения"
|
||||
},
|
||||
"model": {
|
||||
"pinned": "Закреплено",
|
||||
"search": "Поиск моделей...",
|
||||
"stream_output": "Потоковый вывод",
|
||||
"type": {
|
||||
"select": "Выберите тип модели",
|
||||
"text": "Текст",
|
||||
"vision": "Изображение"
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
|
||||
"keep_alive_time.placeholder": "Минуты",
|
||||
@@ -316,6 +316,7 @@
|
||||
"settings": {
|
||||
"about": "О программе и обратная связь",
|
||||
"about.checkUpdate": "Проверить обновления",
|
||||
"about.checkUpdate.available": "Обновить",
|
||||
"about.checkingUpdate": "Проверка обновлений...",
|
||||
"about.contact.button": "Электронная почта",
|
||||
"about.contact.title": "Контакты",
|
||||
@@ -328,10 +329,12 @@
|
||||
"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": "Ассистент по умолчанию",
|
||||
@@ -355,6 +358,8 @@
|
||||
"webdav.password": "Пароль WebDAV",
|
||||
"webdav.path": "Путь WebDAV",
|
||||
"webdav.path.placeholder": "/backup",
|
||||
"webdav.autoSync": "Автоматическая синхронизация",
|
||||
"webdav.minutes": "минут",
|
||||
"webdav.restore.button": "Восстановление с WebDAV",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "Пользователь WebDAV"
|
||||
@@ -372,17 +377,26 @@
|
||||
"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": "Использовать serif шрифт",
|
||||
"messages.input.paste_long_text_threshold": "Длина вставки длинного текста",
|
||||
"model": "Модель по умолчанию",
|
||||
"models.add.add_model": "Добавить модель",
|
||||
"models.add.group_name": "Имя группы",
|
||||
@@ -404,6 +418,7 @@
|
||||
"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",
|
||||
@@ -465,9 +480,12 @@
|
||||
"reset_defaults": "Сбросить настройки по умолчанию",
|
||||
"reset_defaults_confirm": "Вы уверены, что хотите сбросить все горячие клавиши?",
|
||||
"press_shortcut": "Нажмите сочетание клавиш",
|
||||
"alt_warning": "Mac не поддерживает Alt",
|
||||
"alt_warning": "Mac не поддерживает Option + буквы как горячие клавиши",
|
||||
"reset_to_default": "Сбросить настройки по умолчанию",
|
||||
"clear_shortcut": "Очистить сочетание клавиш"
|
||||
"clear_shortcut": "Очистить сочетание клавиш",
|
||||
"toggle_show_assistants": "Переключить отображение ассистентов",
|
||||
"toggle_show_topics": "Переключить отображение топиков",
|
||||
"copy_last_message": "Копировать последнее сообщение"
|
||||
},
|
||||
"theme.auto": "Автоматически",
|
||||
"theme.dark": "Темная",
|
||||
@@ -504,7 +522,83 @@
|
||||
},
|
||||
"words": {
|
||||
"knowledgeGraph": "Граф знаний",
|
||||
"visualization": "Визуализация"
|
||||
"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": "Введите путь к директории",
|
||||
"model_info": "Модель информации",
|
||||
"not_support": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
|
||||
"source": "Источник"
|
||||
},
|
||||
"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-символьном заголовке, совпадающем с языком пользователя, без использования знаков препинания и других специальных символов"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"input.estimated_tokens.tip": "预估 token 数",
|
||||
"input.expand": "展开",
|
||||
"input.new.context": "清除上下文",
|
||||
"input.new_topic": "新话题 {{Command}}+N",
|
||||
"input.new_topic": "新话题 {{Command}}",
|
||||
"input.pause": "暂停",
|
||||
"input.placeholder": "在这里输入消息...",
|
||||
"input.send": "发送",
|
||||
@@ -80,8 +80,11 @@
|
||||
"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": "代码块可折叠",
|
||||
@@ -95,6 +98,8 @@
|
||||
"settings.show_line_numbers": "代码显示行号",
|
||||
"settings.temperature": "模型温度",
|
||||
"settings.temperature.tip": "模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7",
|
||||
"settings.top_p": "Top-P",
|
||||
"settings.top_p.tip": "默认值为 1,值越小,AI 生成的内容越单调,也越容易理解;值越大,AI 回复的词汇围越大,越多样化",
|
||||
"suggestions.title": "建议的问题",
|
||||
"topics.auto_rename": "生成话题名",
|
||||
"topics.clear.title": "清空消息",
|
||||
@@ -107,7 +112,8 @@
|
||||
"topics.list": "话题列表",
|
||||
"topics.move_to": "移动到",
|
||||
"topics.title": "话题",
|
||||
"translate": "翻译"
|
||||
"translate": "翻译",
|
||||
"resend": "重新发送"
|
||||
},
|
||||
"common": {
|
||||
"and": "和",
|
||||
@@ -144,7 +150,8 @@
|
||||
"warning": "警告",
|
||||
"you": "用户",
|
||||
"clear": "清除",
|
||||
"add": "添加"
|
||||
"add": "添加",
|
||||
"footnotes": "引用内容"
|
||||
},
|
||||
"error": {
|
||||
"backup.file_format": "备份文件格式错误",
|
||||
@@ -218,11 +225,13 @@
|
||||
"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.enter.name": "请输入知识库名称",
|
||||
"error.invalid.proxy.url": "无效的代理地址",
|
||||
"error.invalid.webdav": "无效的 WebDAV 设置",
|
||||
"message.code_style": "代码风格",
|
||||
@@ -241,21 +250,13 @@
|
||||
"upgrade.success.button": "重启",
|
||||
"upgrade.success.content": "重启用以完成升级",
|
||||
"upgrade.success.title": "升级成功",
|
||||
"regenerate.confirm": "重新生成会覆盖当前消息"
|
||||
"regenerate.confirm": "重新生成会覆盖当前消息",
|
||||
"copy.success": "复制成功",
|
||||
"error.get_embedding_dimensions": "获取嵌入维度失败"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
},
|
||||
"model": {
|
||||
"pinned": "已固定",
|
||||
"search": "搜索模型...",
|
||||
"stream_output": "流式输出",
|
||||
"type": {
|
||||
"select": "选择模型类型",
|
||||
"text": "文本",
|
||||
"vision": "图像"
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)",
|
||||
"keep_alive_time.placeholder": "分钟",
|
||||
@@ -316,6 +317,7 @@
|
||||
"settings": {
|
||||
"about": "关于我们",
|
||||
"about.checkUpdate": "检查更新",
|
||||
"about.checkUpdate.available": "立即更新",
|
||||
"about.checkingUpdate": "正在检查更新...",
|
||||
"about.contact.button": "邮件",
|
||||
"about.contact.title": "邮件联系",
|
||||
@@ -328,10 +330,12 @@
|
||||
"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": "默认助手",
|
||||
@@ -355,6 +359,8 @@
|
||||
"webdav.password": "WebDAV 密码",
|
||||
"webdav.path": "WebDAV 路径",
|
||||
"webdav.path.placeholder": "/backup",
|
||||
"webdav.autoSync": "自动同步",
|
||||
"webdav.minutes": "分钟",
|
||||
"webdav.restore.button": "从 WebDAV 恢复",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV 用户名"
|
||||
@@ -372,17 +378,26 @@
|
||||
"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": "显示预估 Token 数",
|
||||
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||
"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": "分组名称",
|
||||
@@ -404,6 +419,7 @@
|
||||
"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",
|
||||
@@ -445,7 +461,7 @@
|
||||
"action": "操作",
|
||||
"key": "按键",
|
||||
"new_topic": "新建话题",
|
||||
"title": "快捷键",
|
||||
"title": "快捷方式",
|
||||
"zoom_in": "放大界面",
|
||||
"zoom_out": "缩小界面",
|
||||
"zoom_reset": "重置缩放",
|
||||
@@ -453,9 +469,12 @@
|
||||
"reset_defaults": "重置默认快捷键",
|
||||
"reset_defaults_confirm": "确定要重置所有快捷键吗?",
|
||||
"press_shortcut": "按下快捷键",
|
||||
"alt_warning": "Mac 系统不支持 Alt 键",
|
||||
"alt_warning": "Mac 系统不能使用 Option + 字母作为快捷键",
|
||||
"reset_to_default": "重置为默认",
|
||||
"clear_shortcut": "清除快捷键"
|
||||
"clear_shortcut": "清除快捷键",
|
||||
"toggle_show_assistants": "切换助手显示",
|
||||
"toggle_show_topics": "切换话题显示",
|
||||
"copy_last_message": "复制上一条消息"
|
||||
},
|
||||
"theme.auto": "跟随系统",
|
||||
"theme.dark": "深色主题",
|
||||
@@ -492,7 +511,83 @@
|
||||
},
|
||||
"words": {
|
||||
"knowledgeGraph": "知识图谱",
|
||||
"visualization": "可视化"
|
||||
"visualization": "可视化",
|
||||
"show_window": "显示窗口",
|
||||
"quit": "退出"
|
||||
},
|
||||
"knowledge_base": {
|
||||
"title": "知识库",
|
||||
"search": "搜索知识库",
|
||||
"empty": "暂无知识库",
|
||||
"drag_file": "拖拽文件到这里",
|
||||
"file_hint": "支持 {{file_types}} 格式",
|
||||
"add": {
|
||||
"title": "添加知识库"
|
||||
},
|
||||
"notes": "笔记",
|
||||
"notes_placeholder": "输入此知识库的附加信息或上下文...",
|
||||
"delete": "删除",
|
||||
"rename": "重命名",
|
||||
"urls": "网址",
|
||||
"add_url": "添加网址",
|
||||
"url_placeholder": "请输入网址",
|
||||
"invalid_url": "无效的网址",
|
||||
"add_file": "添加文件",
|
||||
"status": "状态",
|
||||
"index_all": "索引全部",
|
||||
"index_started": "索引开始",
|
||||
"cancel_index": "取消索引",
|
||||
"index_cancelled": "索引已取消",
|
||||
"status_new": "已添加",
|
||||
"status_pending": "等待中",
|
||||
"status_processing": "处理中",
|
||||
"status_completed": "已完成",
|
||||
"status_failed": "失败",
|
||||
"url_added": "网址已添加",
|
||||
"search_placeholder": "输入查询内容",
|
||||
"add_note": "添加笔记",
|
||||
"no_bases": "暂无知识库",
|
||||
"clear_selection": "清除选择",
|
||||
"delete_confirm": "确定要删除此知识库吗?",
|
||||
"sitemaps": "网站",
|
||||
"add_sitemap": "站点地图",
|
||||
"sitemap_placeholder": "请输入站点地图 URL",
|
||||
"directories": "目录",
|
||||
"add_directory": "添加目录",
|
||||
"directory_placeholder": "请输入目录路径",
|
||||
"model_info": "模型信息",
|
||||
"not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库",
|
||||
"source": "来源"
|
||||
},
|
||||
"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 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"input.estimated_tokens.tip": "預估 Token 數",
|
||||
"input.expand": "展開",
|
||||
"input.new.context": "清除上下文",
|
||||
"input.new_topic": "新話題 {{Command}}+N",
|
||||
"input.new_topic": "新話題 {{Command}}",
|
||||
"input.pause": "暫停",
|
||||
"input.placeholder": "在此輸入您的訊息...",
|
||||
"input.send": "發送",
|
||||
@@ -80,8 +80,11 @@
|
||||
"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": "代码块可折叠",
|
||||
@@ -95,6 +98,8 @@
|
||||
"settings.show_line_numbers": "代码顯示行號",
|
||||
"settings.temperature": "溫度",
|
||||
"settings.temperature.tip": "較低的值使模型更具創造性和不可預測性,較高的值則使其更具確定性和精確性。",
|
||||
"settings.top_p": "Top-P",
|
||||
"settings.top_p.tip": "模型生成文本的隨機程度。值越小,AI 生成的內容越單調,也越容易理解;值越大,AI 回覆的詞彙範圍越大,越多樣化",
|
||||
"suggestions.title": "建議的問題",
|
||||
"topics.auto_rename": "自動重新命名",
|
||||
"topics.clear.title": "清空消息",
|
||||
@@ -107,7 +112,8 @@
|
||||
"topics.list": "話題列表",
|
||||
"topics.move_to": "移動到",
|
||||
"topics.title": "話題",
|
||||
"translate": "翻譯"
|
||||
"translate": "翻譯",
|
||||
"resend": "重新發送"
|
||||
},
|
||||
"common": {
|
||||
"and": "與",
|
||||
@@ -126,7 +132,6 @@
|
||||
"download": "下載",
|
||||
"duplicate": "複製",
|
||||
"edit": "編輯",
|
||||
"footnotes": "引用",
|
||||
"language": "語言",
|
||||
"model": "模型",
|
||||
"models": "模型",
|
||||
@@ -144,7 +149,8 @@
|
||||
"warning": "警告",
|
||||
"you": "您",
|
||||
"clear": "清除",
|
||||
"add": "添加"
|
||||
"add": "添加",
|
||||
"footnotes": "引用"
|
||||
},
|
||||
"error": {
|
||||
"backup.file_format": "備份文件格式錯誤",
|
||||
@@ -162,7 +168,7 @@
|
||||
"conversation_details": "會話詳情",
|
||||
"conversation_history": "會話歷史",
|
||||
"created": "創建時間",
|
||||
"last_updated": "最後<EFBFBD><EFBFBD>新",
|
||||
"last_updated": "最後更新",
|
||||
"messages": "訊息數",
|
||||
"user": "用戶"
|
||||
},
|
||||
@@ -218,11 +224,13 @@
|
||||
"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.enter.name": "請先輸入知識庫名稱",
|
||||
"error.invalid.proxy.url": "無效的代理 URL",
|
||||
"error.invalid.webdav": "無效的 WebDAV 設定",
|
||||
"message.code_style": "程式碼風格",
|
||||
@@ -241,21 +249,13 @@
|
||||
"upgrade.success.button": "重新啟動",
|
||||
"upgrade.success.content": "請重新啟動應用以完成升級",
|
||||
"upgrade.success.title": "升級成功",
|
||||
"regenerate.confirm": "重新生成會覆蓋當前訊息"
|
||||
"regenerate.confirm": "重新生成會覆蓋當前訊息",
|
||||
"copy.success": "複製成功",
|
||||
"error.get_embedding_dimensions": "獲取嵌入維度失敗"
|
||||
},
|
||||
"minapp": {
|
||||
"title": "小程序"
|
||||
},
|
||||
"model": {
|
||||
"pinned": "已固定",
|
||||
"search": "搜尋模型...",
|
||||
"stream_output": "串流輸出",
|
||||
"type": {
|
||||
"select": "選擇模型類型",
|
||||
"text": "文字",
|
||||
"vision": "圖像"
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
@@ -316,6 +316,7 @@
|
||||
"settings": {
|
||||
"about": "關於與回饋",
|
||||
"about.checkUpdate": "檢查更新",
|
||||
"about.checkUpdate.available": "立即更新",
|
||||
"about.checkingUpdate": "正在檢查更新...",
|
||||
"about.contact.button": "郵件",
|
||||
"about.contact.title": "聯繫方式",
|
||||
@@ -328,10 +329,12 @@
|
||||
"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": "預設助手",
|
||||
@@ -355,6 +358,8 @@
|
||||
"webdav.password": "WebDAV 密碼",
|
||||
"webdav.path": "WebDAV Path",
|
||||
"webdav.path.placeholder": "/backup",
|
||||
"webdav.autoSync": "自動同步",
|
||||
"webdav.minutes": "分鐘",
|
||||
"webdav.restore.button": "從 WebDAV 恢復",
|
||||
"webdav.title": "WebDAV",
|
||||
"webdav.user": "WebDAV 使用者名稱"
|
||||
@@ -372,17 +377,26 @@
|
||||
"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": "顯示預估 Token 數",
|
||||
"messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||
"messages.input.title": "輸入設定",
|
||||
"messages.math_engine": "Markdown 渲染輸入訊息",
|
||||
"messages.math_render_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": "群組名稱",
|
||||
@@ -404,6 +418,7 @@
|
||||
"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",
|
||||
@@ -453,9 +468,12 @@
|
||||
"reset_defaults": "重置預設快捷鍵",
|
||||
"reset_defaults_confirm": "確定要重置所有快捷鍵嗎?",
|
||||
"press_shortcut": "按下快捷鍵",
|
||||
"alt_warning": "Mac 系統不支持 Alt 鍵",
|
||||
"alt_warning": "Mac 不能使用 Option + 字母作為快捷鍵",
|
||||
"reset_to_default": "重置為預設",
|
||||
"clear_shortcut": "清除快捷鍵"
|
||||
"clear_shortcut": "清除快捷鍵",
|
||||
"toggle_show_assistants": "切換助手顯示",
|
||||
"toggle_show_topics": "切換話題顯示",
|
||||
"copy_last_message": "複製上一条消息"
|
||||
},
|
||||
"theme.auto": "自動",
|
||||
"theme.dark": "深色主題",
|
||||
@@ -492,7 +510,83 @@
|
||||
},
|
||||
"words": {
|
||||
"knowledgeGraph": "知識圖譜",
|
||||
"visualization": "可視化"
|
||||
"visualization": "可視化",
|
||||
"show_window": "顯示視窗",
|
||||
"quit": "退出"
|
||||
},
|
||||
"knowledge_base": {
|
||||
"title": "知識庫",
|
||||
"search": "搜尋知識庫",
|
||||
"empty": "暫無知識庫",
|
||||
"drag_file": "拖拽文件到這裡",
|
||||
"file_hint": "支持 {{file_types}} 格式",
|
||||
"add": {
|
||||
"title": "添加知識庫"
|
||||
},
|
||||
"notes": "筆記",
|
||||
"notes_placeholder": "輸入此知識庫的附加資訊或上下文...",
|
||||
"delete": "刪除",
|
||||
"rename": "重命名",
|
||||
"urls": "網址",
|
||||
"add_url": "添加網址",
|
||||
"url_placeholder": "請輸入網址",
|
||||
"invalid_url": "無效的網址",
|
||||
"add_file": "添加文件",
|
||||
"status": "狀態",
|
||||
"index_all": "索引全部",
|
||||
"index_started": "索引開始",
|
||||
"cancel_index": "取消索引",
|
||||
"index_cancelled": "索引已取消",
|
||||
"status_new": "已添加",
|
||||
"status_pending": "等待中",
|
||||
"status_processing": "處理中",
|
||||
"status_completed": "已完成",
|
||||
"status_failed": "失敗",
|
||||
"url_added": "網址已添加",
|
||||
"search_placeholder": "輸入查詢內容",
|
||||
"add_note": "添加筆記",
|
||||
"no_bases": "暫無知識庫",
|
||||
"clear_selection": "清除選擇",
|
||||
"delete_confirm": "確定要刪除此知識庫嗎?",
|
||||
"sitemaps": "網站",
|
||||
"add_sitemap": "網站地圖",
|
||||
"sitemap_placeholder": "請輸入網站地圖 URL",
|
||||
"directories": "目錄",
|
||||
"add_directory": "添加目錄",
|
||||
"directory_placeholder": "請輸入目錄路徑",
|
||||
"model_info": "模型信息",
|
||||
"not_support": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫",
|
||||
"source": "來源"
|
||||
},
|
||||
"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 個字以內的標題,標題語言與用戶的首要語言一致,不要使用標點符號和其他特殊符號"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ const AgentsPage: FC = () => {
|
||||
{Object.values(filteredAgentGroups)
|
||||
.flat()
|
||||
.map((agent, index, array) => (
|
||||
<Col span={array.length === 1 ? 8 : 6} key={index}>
|
||||
<Col span={array.length === 1 ? 12 : 6} key={index}>
|
||||
<AgentCard
|
||||
onClick={() => onAddAgentConfirm(getAgentFromSystemAgent(agent as any))}
|
||||
agent={agent as any}
|
||||
@@ -290,6 +290,7 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
|
||||
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
|
||||
|
||||
@@ -221,6 +221,7 @@ const SideNav = styled.div`
|
||||
width: var(--assistants-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
padding: 7px 12px;
|
||||
user-select: none;
|
||||
|
||||
.ant-menu {
|
||||
border-inline-end: none !important;
|
||||
|
||||
@@ -21,6 +21,7 @@ let _message: Message | undefined
|
||||
const TopicsPage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [search, setSearch] = useState(_search)
|
||||
const [searchKeywords, setSearchKeywords] = useState(_search)
|
||||
const [stack, setStack] = useState<Route[]>(_stack)
|
||||
const [topic, setTopic] = useState<Topic | undefined>(_topic)
|
||||
const [message, setMessage] = useState<Message | undefined>(_message)
|
||||
@@ -40,6 +41,7 @@ const TopicsPage: FC = () => {
|
||||
}
|
||||
|
||||
const onSearch = () => {
|
||||
setSearchKeywords(search)
|
||||
setStack(['topics', 'search'])
|
||||
setTopic(undefined)
|
||||
}
|
||||
@@ -84,7 +86,7 @@ const TopicsPage: FC = () => {
|
||||
/>
|
||||
<TopicMessages topic={topic} style={{ display: isShow('topic') }} />
|
||||
<SearchResults
|
||||
keywords={isShow('search') ? search : ''}
|
||||
keywords={isShow('search') ? searchKeywords : ''}
|
||||
onMessageClick={onMessageClick}
|
||||
onTopicClick={onTopicClick}
|
||||
style={{ display: isShow('search') }}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
|
||||
import { locateToMessage } from '@renderer/services/MessagesService'
|
||||
import NavigationService from '@renderer/services/NavigationService'
|
||||
@@ -15,6 +16,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
const navigate = NavigationService.navigate!
|
||||
const { messageStyle } = useSettings()
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!message) {
|
||||
@@ -22,7 +24,7 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<MessagesContainer {...props}>
|
||||
<MessagesContainer {...props} className={messageStyle}>
|
||||
<ContainerWrapper style={{ paddingTop: 20, paddingBottom: 20, position: 'relative' }}>
|
||||
<MessageItem message={message} />
|
||||
<Button
|
||||
@@ -45,6 +47,7 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
|
||||
const MessagesContainer = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow-y: scroll;
|
||||
|
||||
@@ -11,19 +11,16 @@ interface Props {
|
||||
files: FileType[]
|
||||
setFiles: (files: FileType[]) => void
|
||||
ToolbarButton: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const AttachmentButton: FC<Props> = ({ model, files, setFiles, ToolbarButton }) => {
|
||||
const AttachmentButton: FC<Props> = ({ model, files, setFiles, ToolbarButton, disabled }) => {
|
||||
const { t } = useTranslation()
|
||||
const extensions = isVisionModel(model)
|
||||
? [...imageExts, ...documentExts, ...textExts]
|
||||
: [...documentExts, ...textExts]
|
||||
|
||||
const onSelectFile = async () => {
|
||||
if (files.length > 0) {
|
||||
return setFiles([])
|
||||
}
|
||||
|
||||
const _files = await window.api.file.select({
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
@@ -34,12 +31,14 @@ const AttachmentButton: FC<Props> = ({ model, files, setFiles, ToolbarButton })
|
||||
]
|
||||
})
|
||||
|
||||
_files && setFiles(_files)
|
||||
if (_files) {
|
||||
setFiles([...files, ..._files])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.upload')} arrow>
|
||||
<ToolbarButton type="text" className={files.length ? 'active' : ''} onClick={onSelectFile}>
|
||||
<ToolbarButton type="text" className={files.length ? 'active' : ''} onClick={onSelectFile} disabled={disabled}>
|
||||
<PaperClipOutlined style={{ rotate: '135deg' }} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -4,18 +4,18 @@ import {
|
||||
FormOutlined,
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
GlobalOutlined,
|
||||
PauseCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { PicCenterOutlined } from '@ant-design/icons'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import { isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
|
||||
import { useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/AssistantService'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
@@ -24,7 +24,7 @@ import { estimateTextTokens as estimateTxtTokens } from '@renderer/services/Toke
|
||||
import { translateText } from '@renderer/services/TranslateService'
|
||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setGenerating, setSearching } from '@renderer/store/runtime'
|
||||
import { Assistant, FileType, Message, Topic } from '@renderer/types'
|
||||
import { Assistant, FileType, KnowledgeBase, Message, Topic } from '@renderer/types'
|
||||
import { delay, getFileExtension, uuid } from '@renderer/utils'
|
||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
import { Button, Popconfirm, Tooltip } from 'antd'
|
||||
@@ -37,6 +37,7 @@ import styled from 'styled-components'
|
||||
|
||||
import AttachmentButton from './AttachmentButton'
|
||||
import AttachmentPreview from './AttachmentPreview'
|
||||
import KnowledgeBaseButton from './KnowledgeBaseButton'
|
||||
import SendMessageButton from './SendMessageButton'
|
||||
import TokenCount from './TokenCount'
|
||||
|
||||
@@ -47,15 +48,17 @@ interface Props {
|
||||
|
||||
let _text = ''
|
||||
let _files: FileType[] = []
|
||||
let _base: KnowledgeBase | undefined
|
||||
|
||||
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
const [text, setText] = useState(_text)
|
||||
const [inputFocus, setInputFocus] = useState(false)
|
||||
const { addTopic, model, setModel } = useAssistant(assistant.id)
|
||||
const { assistant, addTopic, model, setModel, updateAssistant } = useAssistant(_assistant.id)
|
||||
const {
|
||||
sendMessageShortcut,
|
||||
fontSize,
|
||||
pasteLongTextAsFile,
|
||||
pasteLongTextThreshold,
|
||||
showInputEstimatedTokens,
|
||||
clickAssistantToShowTopic,
|
||||
language,
|
||||
@@ -76,6 +79,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||
const spaceClickTimer = useRef<NodeJS.Timeout>()
|
||||
const [isTranslating, setIsTranslating] = useState(false)
|
||||
const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState<KnowledgeBase | undefined>(_base)
|
||||
|
||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
||||
@@ -85,16 +89,19 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
() => (showInputEstimatedTokens ? estimateTextTokens(text) || 0 : 0),
|
||||
[estimateTextTokens, showInputEstimatedTokens, text]
|
||||
)
|
||||
const newTopicShortcut = useShortcutDisplay('new_topic')
|
||||
const inputEmpty = isEmpty(text.trim()) && files.length === 0
|
||||
|
||||
_text = text
|
||||
_files = files
|
||||
_base = selectedKnowledgeBase
|
||||
|
||||
const sendMessage = useCallback(async () => {
|
||||
if (generating) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isEmpty(text.trim())) {
|
||||
if (inputEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,6 +116,10 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
status: 'success'
|
||||
}
|
||||
|
||||
if (selectedKnowledgeBase) {
|
||||
message.knowledgeBaseIds = [selectedKnowledgeBase.id]
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
message.files = await FileManager.uploadFiles(files)
|
||||
}
|
||||
@@ -121,7 +132,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
setTimeout(() => resizeTextArea(), 0)
|
||||
|
||||
setExpend(false)
|
||||
}, [assistant.id, assistant.topics, generating, files, text])
|
||||
}, [generating, inputEmpty, text, assistant.id, assistant.topics, selectedKnowledgeBase, files])
|
||||
|
||||
const translate = async () => {
|
||||
if (isTranslating) {
|
||||
@@ -183,6 +194,16 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) {
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
|
||||
if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) {
|
||||
sendMessage()
|
||||
return event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const addNewTopic = useCallback(async () => {
|
||||
@@ -280,7 +301,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
const item = event.clipboardData?.items[0]
|
||||
if (item && item.kind === 'string' && item.type === 'text/plain') {
|
||||
item.getAsString(async (pasteText) => {
|
||||
if (pasteText.length > 1500) {
|
||||
if (pasteText.length > pasteLongTextThreshold) {
|
||||
const tempFilePath = await window.api.file.create('pasted_text.txt')
|
||||
await window.api.file.write(tempFilePath, pasteText)
|
||||
const selectedFile = await window.api.file.get(tempFilePath)
|
||||
@@ -292,7 +313,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[pasteLongTextAsFile, supportExts, text]
|
||||
[pasteLongTextAsFile, pasteLongTextThreshold, supportExts, text]
|
||||
)
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
@@ -360,6 +381,12 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const textareaRows = window.innerHeight >= 1000 || isBubbleStyle ? 2 : 1
|
||||
|
||||
const handleKnowledgeBaseSelect = (base?: KnowledgeBase) => {
|
||||
setSelectedKnowledgeBase(base)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container onDragOver={handleDragOver} onDrop={handleDrop}>
|
||||
<AttachmentPreview files={files} setFiles={setFiles} />
|
||||
@@ -372,7 +399,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
autoFocus
|
||||
contextMenu="true"
|
||||
variant="borderless"
|
||||
rows={isBubbleStyle ? 2 : 1}
|
||||
rows={textareaRows}
|
||||
ref={textareaRef}
|
||||
style={{ fontSize }}
|
||||
styles={{ textarea: TextareaStyle }}
|
||||
@@ -385,11 +412,22 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarMenu>
|
||||
<Tooltip placement="top" title={t('chat.input.new_topic', { Command: isMac ? '⌘' : 'Ctrl' })} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.new_topic', { Command: newTopicShortcut })} arrow>
|
||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||
<FormOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
{isWebSearchModel(model) && (
|
||||
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
||||
<ToolbarButton
|
||||
type="text"
|
||||
onClick={() => updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })}>
|
||||
<GlobalOutlined
|
||||
style={{ color: assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)' }}
|
||||
/>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||
<Popconfirm
|
||||
title={t('chat.input.clear.content')}
|
||||
@@ -413,7 +451,19 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
<ControlOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<AttachmentButton model={model} files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} />
|
||||
<KnowledgeBaseButton
|
||||
selectedBase={selectedKnowledgeBase}
|
||||
onSelect={handleKnowledgeBaseSelect}
|
||||
ToolbarButton={ToolbarButton}
|
||||
disabled={files.length > 0}
|
||||
/>
|
||||
<AttachmentButton
|
||||
model={model}
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
ToolbarButton={ToolbarButton}
|
||||
disabled={!!selectedKnowledgeBase}
|
||||
/>
|
||||
<ToolbarButton type="text" onClick={onNewContext}>
|
||||
<Tooltip placement="top" title={t('chat.input.new.context')}>
|
||||
<PicCenterOutlined />
|
||||
@@ -443,7 +493,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || !text} />}
|
||||
{!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || inputEmpty} />}
|
||||
</ToolbarMenu>
|
||||
</Toolbar>
|
||||
</InputBarContainer>
|
||||
@@ -457,7 +507,7 @@ const Container = styled.div`
|
||||
`
|
||||
|
||||
const InputBarContainer = styled.div`
|
||||
border: 1px solid var(--color-border-soft);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
margin: 0 20px 15px 20px;
|
||||
|
||||
83
src/renderer/src/pages/home/Inputbar/KnowledgeBaseButton.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { FileSearchOutlined } from '@ant-design/icons'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { KnowledgeBase } from '@renderer/types'
|
||||
import { Button, Popover, Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
selectedBase?: KnowledgeBase
|
||||
onSelect: (base?: KnowledgeBase) => void
|
||||
disabled?: boolean
|
||||
ToolbarButton?: any
|
||||
}
|
||||
|
||||
const KnowledgeBaseSelector: FC<Props> = ({ selectedBase, onSelect }) => {
|
||||
const { t } = useTranslation()
|
||||
const knowledgeState = useAppSelector((state) => state.knowledge)
|
||||
|
||||
return (
|
||||
<SelectorContainer>
|
||||
{knowledgeState.bases.length === 0 ? (
|
||||
<EmptyMessage>{t('knowledge.no_bases')}</EmptyMessage>
|
||||
) : (
|
||||
<>
|
||||
{selectedBase && (
|
||||
<Button type="link" block onClick={() => onSelect(undefined)} style={{ textAlign: 'left' }}>
|
||||
{t('knowledge.clear_selection')}
|
||||
</Button>
|
||||
)}
|
||||
{knowledgeState.bases.map((base) => (
|
||||
<Button
|
||||
key={base.id}
|
||||
type={selectedBase?.id === base.id ? 'primary' : 'text'}
|
||||
block
|
||||
onClick={() => onSelect(base)}
|
||||
style={{ textAlign: 'left' }}>
|
||||
{base.name}
|
||||
</Button>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectorContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const KnowledgeBaseButton: FC<Props> = ({ selectedBase, onSelect, disabled, ToolbarButton }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (selectedBase) {
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.knowledge_base')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => onSelect(undefined)}>
|
||||
<FileSearchOutlined style={{ color: selectedBase ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.knowledge_base')} arrow>
|
||||
<Popover
|
||||
placement="top"
|
||||
content={<KnowledgeBaseSelector selectedBase={selectedBase} onSelect={onSelect} />}
|
||||
trigger="click">
|
||||
<ToolbarButton type="text" onClick={() => selectedBase && onSelect(undefined)} disabled={disabled}>
|
||||
<FileSearchOutlined style={{ color: selectedBase ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
</ToolbarButton>
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const SelectorContainer = styled.div`
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
`
|
||||
|
||||
const EmptyMessage = styled.div`
|
||||
padding: 8px;
|
||||
`
|
||||
|
||||
export default KnowledgeBaseButton
|
||||
@@ -8,6 +8,7 @@ import styled from 'styled-components'
|
||||
|
||||
import Artifacts from './Artifacts'
|
||||
import Mermaid from './Mermaid'
|
||||
import SvgPreview from './SvgPreview'
|
||||
|
||||
interface CodeBlockProps {
|
||||
children: string
|
||||
@@ -79,8 +80,20 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
return <Mermaid chart={children} />
|
||||
}
|
||||
|
||||
if (language === 'svg') {
|
||||
return (
|
||||
<CodeBlockWrapper className="code-block">
|
||||
<CodeHeader>
|
||||
<CodeLanguage>{'<SVG>'}</CodeLanguage>
|
||||
<CopyButton text={children} />
|
||||
</CodeHeader>
|
||||
<SvgPreview>{children}</SvgPreview>
|
||||
</CodeBlockWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return match ? (
|
||||
<div className="code-block">
|
||||
<CodeBlockWrapper className="code-block">
|
||||
<CodeHeader>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
{codeCollapsible && shouldShowExpandButton && (
|
||||
@@ -118,7 +131,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
</CodeFooter>
|
||||
)}
|
||||
{language === 'html' && children?.includes('</html>') && <Artifacts html={children} />}
|
||||
</div>
|
||||
</CodeBlockWrapper>
|
||||
) : (
|
||||
<code className={className}>{children}</code>
|
||||
)
|
||||
@@ -142,6 +155,8 @@ const CopyButton: React.FC<{ text: string; style?: React.CSSProperties }> = ({ t
|
||||
)
|
||||
}
|
||||
|
||||
const CodeBlockWrapper = styled.div``
|
||||
|
||||
const CodeContent = styled.div<{ isShowLineNumbers: boolean }>`
|
||||
.shiki {
|
||||
padding: 1em;
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'katex/dist/katex.min.css'
|
||||
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { Message } from '@renderer/types'
|
||||
import { escapeBrackets } from '@renderer/utils/formula'
|
||||
import { escapeBrackets, removeSvgEmptyLines } from '@renderer/utils/formula'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -19,7 +19,7 @@ import ImagePreview from './ImagePreview'
|
||||
import Link from './Link'
|
||||
|
||||
const ALLOWED_ELEMENTS =
|
||||
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr)/i
|
||||
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan|sub|sup)/i
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
@@ -35,7 +35,7 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
const empty = isEmpty(message.content)
|
||||
const paused = message.status === 'paused'
|
||||
const content = empty && paused ? t('message.chat.completion.paused') : message.content
|
||||
return escapeBrackets(content)
|
||||
return removeSvgEmptyLines(escapeBrackets(content))
|
||||
}, [message.content, message.status, t])
|
||||
|
||||
const rehypePlugins = useMemo(() => {
|
||||
@@ -52,7 +52,6 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
className="markdown"
|
||||
rehypePlugins={rehypePlugins}
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
disallowedElements={mathEngine === 'KaTeX' ? ['style'] : []}
|
||||
components={
|
||||
{
|
||||
a: Link,
|
||||
|
||||
@@ -17,6 +17,7 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
const mermaidId = `mermaid-popup-${Date.now()}`
|
||||
const [activeTab, setActiveTab] = useState('preview')
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
@@ -86,6 +87,11 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(chart)
|
||||
window.message.success(t('message.copy.success'))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window?.mermaid?.contentLoaded()
|
||||
}, [])
|
||||
@@ -101,11 +107,18 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
|
||||
centered
|
||||
footer={[
|
||||
<Space key="download-buttons">
|
||||
<Button onClick={() => handleDownload('svg')}>{t('mermaid.download.svg')}</Button>
|
||||
<Button onClick={() => handleDownload('png')}>{t('mermaid.download.png')}</Button>
|
||||
{activeTab === 'source' && <Button onClick={() => handleCopy()}>{t('common.copy')}</Button>}
|
||||
{activeTab === 'preview' && (
|
||||
<>
|
||||
<Button onClick={() => handleDownload('svg')}>{t('mermaid.download.svg')}</Button>
|
||||
<Button onClick={() => handleDownload('png')}>{t('mermaid.download.png')}</Button>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
]}>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
items={[
|
||||
{
|
||||
key: 'preview',
|
||||
|
||||
16
src/renderer/src/pages/home/Markdown/SvgPreview.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
const SvgPreview = ({ children }: { children: string }) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: children }}
|
||||
style={{
|
||||
padding: '1em',
|
||||
backgroundColor: 'white',
|
||||
border: '0.5px solid var(--color-code-background)',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgPreview
|
||||
@@ -86,9 +86,12 @@ const MessageItem: FC<Props> = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribes = [EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler)]
|
||||
const unsubscribes = [
|
||||
EventEmitter.on(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, messageHighlightHandler),
|
||||
EventEmitter.on(EVENT_NAMES.RESEND_MESSAGE + ':' + message.id, onEditMessage)
|
||||
]
|
||||
return () => unsubscribes.forEach((unsub) => unsub())
|
||||
}, [message])
|
||||
}, [message, onEditMessage])
|
||||
|
||||
useEffect(() => {
|
||||
if (message.role === 'user' && !message.usage) {
|
||||
@@ -136,9 +139,11 @@ const MessageItem: FC<Props> = ({
|
||||
|
||||
if (message.type === 'clear') {
|
||||
return (
|
||||
<Divider dashed style={{ padding: '0 20px' }} plain>
|
||||
{t('chat.message.new.context')}
|
||||
</Divider>
|
||||
<NewContextMessage onClick={() => EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)}>
|
||||
<Divider dashed style={{ padding: '0 20px' }} plain>
|
||||
{t('chat.message.new.context')}
|
||||
</Divider>
|
||||
</NewContextMessage>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -176,6 +181,7 @@ const MessageItem: FC<Props> = ({
|
||||
setModel={setModel}
|
||||
onEditMessage={onEditMessage}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
</MessageFooter>
|
||||
)}
|
||||
@@ -228,4 +234,8 @@ const MessageFooter = styled.div`
|
||||
gap: 20px;
|
||||
`
|
||||
|
||||
const NewContextMessage = styled.div`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
export default memo(MessageItem)
|
||||
|
||||
@@ -35,9 +35,9 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
||||
|
||||
const getUserName = useCallback(() => {
|
||||
if (isLocalAi && message.role !== 'user') return APP_NAME
|
||||
if (message.role === 'assistant') return model?.name || model?.id || ''
|
||||
if (message.role === 'assistant') return model?.name || model?.id || message.modelId || ''
|
||||
return userName || t('common.you')
|
||||
}, [message.role, model?.id, model?.name, t, userName])
|
||||
}, [message.modelId, message.role, model?.id, model?.name, t, userName])
|
||||
|
||||
const isAssistantMessage = message.role === 'assistant'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { translateText } from '@renderer/services/TranslateService'
|
||||
import { Message, Model } from '@renderer/types'
|
||||
import { removeTrailingDoubleSpaces } from '@renderer/utils'
|
||||
import { Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { FC, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -31,6 +31,7 @@ interface Props {
|
||||
setModel: (model: Model) => void
|
||||
onEditMessage?: (message: Message) => void
|
||||
onDeleteMessage?: (message: Message) => void
|
||||
onGetMessages?: () => Message[]
|
||||
}
|
||||
|
||||
const MessageMenubar: FC<Props> = (props) => {
|
||||
@@ -43,7 +44,8 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
assistantModel,
|
||||
setModel,
|
||||
onEditMessage,
|
||||
onDeleteMessage
|
||||
onDeleteMessage,
|
||||
onGetMessages
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
const [copied, setCopied] = useState(false)
|
||||
@@ -75,10 +77,43 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
})
|
||||
}, [index, t])
|
||||
|
||||
const onResend = useCallback(() => {
|
||||
const _messages = onGetMessages?.() || []
|
||||
const index = _messages.findIndex((m) => m.id === message.id)
|
||||
const nextIndex = index + 1
|
||||
const nextMessage = _messages[nextIndex]
|
||||
|
||||
if (nextMessage && nextMessage.role === 'assistant') {
|
||||
EventEmitter.emit(EVENT_NAMES.RESEND_MESSAGE + ':' + nextMessage.id, {
|
||||
...nextMessage,
|
||||
content: '',
|
||||
status: 'sending',
|
||||
modelId: assistantModel?.id || model?.id,
|
||||
translatedContent: undefined
|
||||
})
|
||||
}
|
||||
}, [assistantModel?.id, message.id, model?.id, onGetMessages])
|
||||
|
||||
const onEdit = useCallback(async () => {
|
||||
const editedText = await TextEditPopup.show({ text: message.content })
|
||||
let resendMessage = false
|
||||
|
||||
const editedText = await TextEditPopup.show({
|
||||
text: message.content,
|
||||
children: (props) => (
|
||||
<ReSendButton
|
||||
icon={<i className="iconfont icon-ic_send" style={{ color: 'var(--color-primary)' }} />}
|
||||
onClick={() => {
|
||||
props.onOk?.()
|
||||
resendMessage = true
|
||||
}}>
|
||||
{t('chat.resend')}
|
||||
</ReSendButton>
|
||||
)
|
||||
})
|
||||
|
||||
editedText && onEditMessage?.({ ...message, content: editedText })
|
||||
}, [message, onEditMessage])
|
||||
resendMessage && onResend()
|
||||
}, [message, onEditMessage, onResend, t])
|
||||
|
||||
const handleTranslate = useCallback(
|
||||
async (language: string) => {
|
||||
@@ -133,7 +168,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
onClick: () => handleTranslate('chinese')
|
||||
},
|
||||
{
|
||||
label: '🇹🇼 ' + t('languages.chinese-traditional'),
|
||||
label: '🇭🇰 ' + t('languages.chinese-traditional'),
|
||||
key: 'translate-chinese-traditional',
|
||||
onClick: () => handleTranslate('chinese-traditional')
|
||||
},
|
||||
@@ -205,13 +240,15 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
destroyTooltipOnHide
|
||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||
onConfirm={onDeleteAndRegenerate}>
|
||||
<ActionButton className="message-action-button">
|
||||
<SyncOutlined />
|
||||
</ActionButton>
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<ActionButton className="message-action-button">
|
||||
<SyncOutlined />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
)}
|
||||
{canRegenerate && (
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<Tooltip title={t('chat.message.regenerate.model')} mouseEnterDelay={0.8}>
|
||||
<ActionButton className="message-action-button" onClick={onAtModelRegenerate}>
|
||||
<i className="iconfont icon-at1"></i>
|
||||
</ActionButton>
|
||||
@@ -285,4 +322,10 @@ const ActionButton = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const ReSendButton = styled(Button)`
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
`
|
||||
|
||||
export default MessageMenubar
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { Message } from '@renderer/types'
|
||||
import { t } from 'i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({ message, isLastMessage }) => {
|
||||
@@ -27,9 +28,25 @@ const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({
|
||||
}
|
||||
|
||||
if (message.role === 'assistant') {
|
||||
let metrixs = ''
|
||||
let hasMetrics = false
|
||||
|
||||
if (message?.metrics?.completion_tokens && message?.metrics?.time_completion_millsec) {
|
||||
hasMetrics = true
|
||||
metrixs = t('settings.messages.metrics', {
|
||||
time_first_token_millsec: message?.metrics?.time_first_token_millsec,
|
||||
token_speed: (message?.metrics?.completion_tokens / (message?.metrics?.time_completion_millsec / 1000)).toFixed(
|
||||
0
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageMetadata className="message-tokens" onClick={locateMessage}>
|
||||
Tokens: {message?.usage?.total_tokens} | ↑{message?.usage?.prompt_tokens} | ↓{message?.usage?.completion_tokens}
|
||||
<MessageMetadata className={`message-tokens ${hasMetrics ? 'has-metrics' : ''}`} onClick={locateMessage}>
|
||||
<span className="metrics">{metrixs}</span>
|
||||
<span className="tokens">
|
||||
Tokens: {message?.usage?.total_tokens} ↑{message?.usage?.prompt_tokens} ↓{message?.usage?.completion_tokens}
|
||||
</span>
|
||||
</MessageMetadata>
|
||||
)
|
||||
}
|
||||
@@ -38,11 +55,30 @@ const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({
|
||||
}
|
||||
|
||||
const MessageMetadata = styled.div`
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-2);
|
||||
user-select: text;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
|
||||
.metrics {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tokens {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.has-metrics:hover {
|
||||
.metrics {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tokens {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default MessgeTokens
|
||||
|
||||
@@ -2,6 +2,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||
import { getTopic, TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||
@@ -17,8 +18,10 @@ import { estimateHistoryTokens } from '@renderer/services/TokenService'
|
||||
import { Assistant, Message, Model, Topic } from '@renderer/types'
|
||||
import { captureScrollableDiv, runAsyncFunction, uuid } from '@renderer/utils'
|
||||
import { t } from 'i18next'
|
||||
import { flatten, last, reverse, take } from 'lodash'
|
||||
import { flatten, last, take } from 'lodash'
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import BeatLoader from 'react-spinners/BeatLoader'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Suggestions from '../components/Suggestions'
|
||||
@@ -31,13 +34,53 @@ interface Props {
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
}
|
||||
|
||||
interface LoaderProps {
|
||||
$loading: boolean
|
||||
}
|
||||
|
||||
const LoaderContainer = styled.div<LoaderProps>`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background: var(--color-background);
|
||||
opacity: ${(props) => (props.$loading ? 1 : 0)};
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
`
|
||||
|
||||
const ScrollContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
`
|
||||
|
||||
interface ContainerProps {
|
||||
right?: boolean
|
||||
}
|
||||
|
||||
const Container = styled(Scrollbar)<ContainerProps>`
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
padding: 10px 0;
|
||||
padding-bottom: 20px;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
const [messages, setMessages] = useState<Message[]>([])
|
||||
const [displayMessages, setDisplayMessages] = useState<Message[]>([])
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const messagesRef = useRef(messages)
|
||||
const { updateTopic, addTopic } = useAssistant(assistant.id)
|
||||
const { showTopics, topicPosition, showAssistants, enableTopicNaming } = useSettings()
|
||||
|
||||
const messagesRef = useRef(messages)
|
||||
const INITIAL_MESSAGES_COUNT = 20
|
||||
const LOAD_MORE_COUNT = 20
|
||||
|
||||
messagesRef.current = messages
|
||||
|
||||
const maxWidth = useMemo(() => {
|
||||
@@ -116,6 +159,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
EventEmitter.on(EVENT_NAMES.AI_AUTO_RENAME, autoRenameTopic),
|
||||
EventEmitter.on(EVENT_NAMES.CLEAR_MESSAGES, () => {
|
||||
setMessages([])
|
||||
setDisplayMessages([])
|
||||
const defaultTopic = getDefaultTopic(assistant.id)
|
||||
updateTopic({ ...topic, name: defaultTopic.name, messages: [] })
|
||||
TopicManager.clearTopicMessages(topic.id)
|
||||
@@ -158,7 +202,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
setActiveTopic(newTopic)
|
||||
autoRenameTopic()
|
||||
|
||||
// 由于复制了消息,消息中附带的文件的总数变了,需要更新
|
||||
// 由于复制了消<EFBFBD><EFBFBD><EFBFBD>,消息中附带的文件的总数变了,需要更新
|
||||
const filesArr = branchMessages.map((m) => m.files)
|
||||
const files = flatten(filesArr).filter(Boolean)
|
||||
files.map(async (f) => {
|
||||
@@ -197,7 +241,39 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
})
|
||||
}, [assistant, messages])
|
||||
|
||||
const memoizedMessages = useMemo(() => reverse([...messages]), [messages])
|
||||
// 初始化显示最新的消息
|
||||
useEffect(() => {
|
||||
if (messages.length > 0) {
|
||||
const reversedMessages = [...messages].reverse()
|
||||
setDisplayMessages(reversedMessages.slice(0, INITIAL_MESSAGES_COUNT))
|
||||
setHasMore(messages.length > INITIAL_MESSAGES_COUNT)
|
||||
}
|
||||
}, [messages])
|
||||
|
||||
// 加载更多历史消息
|
||||
const loadMoreMessages = useCallback(() => {
|
||||
if (!hasMore || isLoadingMore) return
|
||||
|
||||
setIsLoadingMore(true)
|
||||
|
||||
setTimeout(() => {
|
||||
const currentLength = displayMessages.length
|
||||
const reversedMessages = [...messages].reverse()
|
||||
const moreMessages = reversedMessages.slice(currentLength, currentLength + LOAD_MORE_COUNT)
|
||||
|
||||
setDisplayMessages((prev) => [...prev, ...moreMessages])
|
||||
setHasMore(currentLength + LOAD_MORE_COUNT < messages.length)
|
||||
setIsLoadingMore(false)
|
||||
}, 300)
|
||||
}, [displayMessages, hasMore, isLoadingMore, messages])
|
||||
|
||||
useShortcut('copy_last_message', () => {
|
||||
const lastMessage = last(messages)
|
||||
if (lastMessage) {
|
||||
navigator.clipboard.writeText(lastMessage.content)
|
||||
window.message.success(t('message.copy.success'))
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Container
|
||||
@@ -207,30 +283,34 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
|
||||
ref={containerRef}
|
||||
right={topicPosition === 'left'}>
|
||||
<Suggestions assistant={assistant} messages={messages} />
|
||||
{memoizedMessages.map((message, index) => (
|
||||
<MessageItem
|
||||
key={message.id}
|
||||
message={message}
|
||||
topic={topic}
|
||||
index={index}
|
||||
hidePresetMessages={assistant.settings?.hideMessages}
|
||||
onSetMessages={setMessages}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
))}
|
||||
<InfiniteScroll
|
||||
dataLength={displayMessages.length}
|
||||
next={loadMoreMessages}
|
||||
hasMore={hasMore}
|
||||
loader={null}
|
||||
inverse={true}
|
||||
scrollableTarget="messages">
|
||||
<ScrollContainer>
|
||||
<LoaderContainer $loading={isLoadingMore}>
|
||||
<BeatLoader size={8} color="var(--color-text-2)" />
|
||||
</LoaderContainer>
|
||||
{displayMessages.map((message, index) => (
|
||||
<MessageItem
|
||||
key={message.id}
|
||||
message={message}
|
||||
topic={topic}
|
||||
index={index}
|
||||
hidePresetMessages={assistant.settings?.hideMessages}
|
||||
onSetMessages={setMessages}
|
||||
onDeleteMessage={onDeleteMessage}
|
||||
onGetMessages={onGetMessages}
|
||||
/>
|
||||
))}
|
||||
</ScrollContainer>
|
||||
</InfiniteScroll>
|
||||
<Prompt assistant={assistant} key={assistant.prompt} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled(Scrollbar)`
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
padding: 10px 0;
|
||||
padding-bottom: 20px;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
export default Messages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AssistantSettingsPopup from '@renderer/components/AssistantSettings'
|
||||
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||