Merge remote-tracking branch 'origin/main'

This commit is contained in:
denislov 2025-02-16 15:13:47 +08:00
commit 8f2299b875
113 changed files with 4837 additions and 1857 deletions

View File

@ -16,6 +16,7 @@ module.exports = {
'react/prop-types': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'react/no-is-mounted': 'off'
'react/no-is-mounted': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }]
}
}

View File

@ -8,6 +8,18 @@ body:
value: |
感谢您花时间填写此错误报告!
- type: checkboxes
id: checklist
attributes:
label: Issue 检查清单
description: |
在提交 Issue 前请确保您已经完成了以下所有步骤
options:
- label: 我已经查看了置顶 Issue 并搜索了现有的 Issue但没有找到类似的问题。
required: true
- label: 正确填写了 Issue 标题。
required: true
- type: dropdown
id: platform
attributes:

View File

@ -8,6 +8,39 @@ body:
value: |
感谢您花时间提出新的功能建议!
- type: checkboxes
id: checklist
attributes:
label: Issue 检查清单
description: |
在提交 Issue 前请确保您已经完成了以下所有步骤
options:
- label: 我已经查看了置顶 Issue 并搜索了现有的 Issue但没有找到类似的问题。
required: true
- label: 正确填写了 Issue 标题。
required: true
- type: dropdown
id: platform
attributes:
label: 平台
description: 您正在使用哪个平台?
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: version
attributes:
label: 版本
description: 您正在运行的 Cherry Studio 版本是什么?
placeholder: 例如 v1.0.0
validations:
required: true
- type: textarea
id: problem
attributes:

View File

@ -8,6 +8,39 @@ body:
value: |
感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。
- type: checkboxes
id: checklist
attributes:
label: Issue 检查清单
description: |
在提交 Issue 前请确保您已经完成了以下所有步骤
options:
- label: 我已经查看了置顶 Issue 并搜索了现有的 Issue但没有找到类似的问题。
required: true
- label: 正确填写了 Issue 标题。
required: true
- type: dropdown
id: platform
attributes:
label: 平台
description: 您正在使用哪个平台?
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: version
attributes:
label: 版本
description: 您正在运行的 Cherry Studio 版本是什么?
placeholder: 例如 v1.0.0
validations:
required: true
- type: textarea
id: question
attributes:

View File

@ -8,6 +8,18 @@ body:
value: |
Thanks for taking the time to fill out this bug report!
- type: checkboxes
id: checklist
attributes:
label: Issue Checklist
description: |
Before submitting an issue, please make sure you have completed the following steps
options:
- label: I have viewed the pinned issues and searched existing issues but couldn't find anything similar.
required: true
- label: I have filled out the issue title correctly.
required: true
- type: dropdown
id: platform
attributes:

View File

@ -8,6 +8,39 @@ body:
value: |
Thanks for taking the time to suggest a new feature!
- type: checkboxes
id: checklist
attributes:
label: Issue Checklist
description: |
Before submitting an issue, please make sure you have completed the following steps
options:
- label: I have viewed the pinned issues and searched existing issues but couldn't find anything similar.
required: true
- label: I have filled out the issue title correctly.
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: What platform are you using?
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of Cherry Studio are you running?
placeholder: e.g. v1.0.0
validations:
required: true
- type: textarea
id: problem
attributes:

View File

@ -8,6 +8,39 @@ body:
value: |
Thanks for asking a question! Please provide as much detail as possible so we can better assist you.
- type: checkboxes
id: checklist
attributes:
label: Issue Checklist
description: |
Before submitting an issue, please make sure you have completed the following steps
options:
- label: I have viewed the pinned issues and searched existing issues but couldn't find anything similar.
required: true
- label: I have filled out the issue title correctly.
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: What platform are you using?
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of Cherry Studio are you running?
placeholder: e.g. v1.0.0
validations:
required: true
- type: textarea
id: question
attributes:

View File

@ -70,6 +70,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
- name: Build Mac
if: matrix.os == 'macos-latest'
@ -82,6 +83,7 @@ jobs:
APPLE_ID: ${{ vars.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ vars.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Windows
@ -89,6 +91,7 @@ jobs:
run: yarn build:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }}
- name: Replace spaces in filenames
run: node scripts/replace-spaces.js

2
.gitignore vendored
View File

@ -44,3 +44,5 @@ stats.html
# Local
local
.aider*
.cursorrules

View File

@ -1,26 +0,0 @@
diff --git a/core.js b/core.js
index 30c91e66bf595a66c09eb3dbcbda7d58154865f5..b511ff24ea1891904c60174c6ed26ecdd4d5ac51 100644
--- a/core.js
+++ b/core.js
@@ -156,7 +156,7 @@ class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/core.mjs b/core.mjs
index ac267bcfcff44b1f7c9bea5513bba94726a31795..dd5bd9f29609d3f0eea4bd5b225f302893df14ad 100644
--- a/core.mjs
+++ b/core.mjs
@@ -149,7 +149,7 @@ export class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}

View File

@ -0,0 +1,39 @@
diff --git a/core.js b/core.js
index e75a18281ce8f051990c5a50bc1076afdddf91a3..e62f796791a155f23d054e74a429516c14d6e11b 100644
--- a/core.js
+++ b/core.js
@@ -156,7 +156,7 @@ class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/core.mjs b/core.mjs
index fcef58eb502664c41a77483a00db8adaf29b2817..18c5d6ed4be86b3640931277bdc27700006764d7 100644
--- a/core.mjs
+++ b/core.mjs
@@ -149,7 +149,7 @@ export class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/error.mjs b/error.mjs
index 7d19f5578040afa004bc887aab1725e8703d2bac..59ec725b6142299a62798ac4bdedb63ba7d9932c 100644
--- a/error.mjs
+++ b/error.mjs
@@ -36,7 +36,7 @@ export class APIError extends OpenAIError {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
- const error = errorResponse?.['error'];
+ const error = errorResponse?.['error'] || errorResponse;
if (status === 400) {
return new BadRequestError(status, error, message, headers);
}

View File

@ -12,7 +12,7 @@
Cherry Studio is a desktop client that supports for multiple LLM providers, available on Windows, Mac and Linux.
👏 Join [Telegram Group](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ Group(534635975)](https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mPMbCwUo40lYODSp-SUeY9ju9sSBeMbS&authKey=Tt8SyX2p4i1Aopn2OzPwi88tc81AW%2F4m%2Fkt4ETHTPGnM6TKOXuRxKJuUMWu5Hgay&noverify=0&group_code=534635975)
👏 Join [Telegram Group](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ Group(1022779719)](https://qm.qq.com/q/Qtw8As0cwe)
❤️ Like Cherry Studio? Give it a star 🌟 or [Sponsor](docs/sponsor.md) to support the development!
@ -30,7 +30,7 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai
- ☁️ Major LLM Cloud Services: OpenAI, Gemini, Anthropic, and more
- 🔗 AI Web Service Integration: Claude, Peplexity, Poe, and others
- 💻 Local Model Support with Ollama
- 💻 Local Model Support with Ollama, LM Studio
2. **AI Assistants & Conversations**:

View File

@ -13,7 +13,7 @@
Cherry Studioは、複数のLLMプロバイダーをサポートするデスクトップクライアントで、Windows、Mac、Linuxで利用可能です。
👏 [Telegram](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQグループ(534635975)](https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mPMbCwUo40lYODSp-SUeY9ju9sSBeMbS&authKey=Tt8SyX2p4i1Aopn2OzPwi88tc81AW%2F4m%2Fkt4ETHTPGnM6TKOXuRxKJuUMWu5Hgay&noverify=0&group_code=534635975)
👏 [Telegram](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQグループ(1022779719)](https://qm.qq.com/q/Qtw8As0cwe)
❤️ Cherry Studioをお気に入りにしましたか小さな星をつけてください 🌟 または [スポンサー](sponsor.md) をして開発をサポートしてください!❤️
@ -31,7 +31,7 @@ Cherry Studioは、複数のLLMプロバイダーをサポートするデスク
- ☁️ 主要な LLM クラウドサービス対応OpenAI、Gemini、Anthropic など
- 🔗 AI Web サービス統合Claude、Peplexity、Poe など
- 💻 Ollama によるローカルモデル実行対応
- 💻 Ollama、LM Studio によるローカルモデル実行対応
2. **AI アシスタントと対話**

View File

@ -13,7 +13,7 @@
Cherry Studio 是一款支持多个大语言模型LLM服务商的桌面客户端兼容 Windows、Mac 和 Linux 系统。
👏 欢迎加入 [Telegram 群组](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ群(534635975)](https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mPMbCwUo40lYODSp-SUeY9ju9sSBeMbS&authKey=Tt8SyX2p4i1Aopn2OzPwi88tc81AW%2F4m%2Fkt4ETHTPGnM6TKOXuRxKJuUMWu5Hgay&noverify=0&group_code=534635975)
👏 欢迎加入 [Telegram 群组](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQ群(1022779719)](https://qm.qq.com/q/Qtw8As0cwe)
❤️ 喜欢 Cherry Studio? 点亮小星星 🌟 或 [赞助开发者](sponsor.md)! ❤️
@ -31,7 +31,7 @@ Cherry Studio 是一款支持多个大语言模型LLM服务商的桌面客
- ☁️ 支持主流 LLM 云服务OpenAI、Gemini、Anthropic、硅基流动等
- 🔗 集成流行 AI Web 服务Claude、Peplexity、Poe、腾讯元宝、知乎直答等
- 💻 支持 Ollama 本地模型部署
- 💻 支持 Ollama、LM Studio 本地模型部署
2. **智能助手与对话**

View File

@ -80,12 +80,11 @@ afterPack: scripts/after-pack.js
afterSign: scripts/notarize.js
releaseInfo:
releaseNotes: |
知识库增加设置功能
更加友好的错误提示信息
增加百度云千帆服务商
修复 Groq deepseek 模型无法使用问题
优化思考过程显示 @jettpark @github
可以为助手绑定一个知识库 @eeee0717
修复知识库搜索崩溃问题 @yrom
优化 o3-mini 模型输出显示问题 @teaim
移除 QwenLM 服务商
增加服务商 LM Studio、魔搭、Perplexity、无问芯穹、DMXAPI
提及功能支持上下按键循环选择模型
小程序增加小艺
增加Notion连接检测功能
编辑模型弹窗搜索模型时同时搜索模型的名字和ID
编辑模型弹窗增加推理模型筛选按钮
修复思考模型思考时间显示错误
修复部分模型翻译出错

View File

@ -51,7 +51,7 @@ export default defineConfig({
}
},
optimizeDeps: {
exclude: ['chunk-PZ64DZKH.js', 'chunk-JMKENWIY.js']
exclude: ['chunk-PZ64DZKH.js', 'chunk-JMKENWIY.js', 'chunk-UXYB6GHG.js']
}
}
})

View File

@ -1,6 +1,6 @@
{
"name": "CherryStudio",
"version": "0.9.21",
"version": "0.9.24",
"private": true,
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",
@ -44,7 +44,8 @@
"generate:agents": "yarn workspace @cherry-studio/database agents",
"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"
"analyze:main": "VISUALIZER_MAIN=true yarn build",
"check": "node scripts/check-i18n.js"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
@ -54,7 +55,6 @@
"@llm-tools/embedjs": "patch:@llm-tools/embedjs@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-npm-0.1.28-8e4393fa2d.patch",
"@llm-tools/embedjs-libsql": "^0.1.28",
"@llm-tools/embedjs-loader-csv": "patch:@llm-tools/embedjs-loader-csv@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-loader-csv-npm-0.1.28-a5dac8addd.patch",
"@llm-tools/embedjs-loader-image": "^0.1.28",
"@llm-tools/embedjs-loader-markdown": "patch:@llm-tools/embedjs-loader-markdown@npm%3A0.1.28#~/.yarn/patches/@llm-tools-embedjs-loader-markdown-npm-0.1.28-81647ffac6.patch",
"@llm-tools/embedjs-loader-msoffice": "^0.1.28",
"@llm-tools/embedjs-loader-pdf": "^0.1.28",
@ -71,6 +71,7 @@
"electron-store": "^8.2.0",
"electron-updater": "^6.3.9",
"electron-window-state": "^5.0.3",
"epub": "^1.3.0",
"fs-extra": "^11.2.0",
"html2canvas": "^1.4.1",
"markdown-it": "^14.1.0",
@ -85,11 +86,13 @@
"@electron-toolkit/tsconfig": "^1.0.1",
"@hello-pangea/dnd": "^16.6.0",
"@kangfenmao/keyv-storage": "^0.1.0",
"@llm-tools/embedjs-loader-image": "^0.1.28",
"@reduxjs/toolkit": "^2.2.5",
"@types/adm-zip": "^0",
"@types/fs-extra": "^11",
"@types/lodash": "^4.17.5",
"@types/markdown-it": "^14",
"@types/md5": "^2.3.5",
"@types/node": "^18.19.9",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
@ -119,7 +122,7 @@
"i18next": "^23.11.5",
"lodash": "^4.17.21",
"mime": "^4.0.4",
"openai": "patch:openai@npm%3A4.76.2#~/.yarn/patches/openai-npm-4.76.2-8ff1374617.patch",
"openai": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch",
"prettier": "^3.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -141,6 +144,7 @@
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.2",
"shiki": "^1.22.2",
"string-width": "^7.2.0",
"styled-components": "^6.1.11",
"tinycolor2": "^1.6.0",
"typescript": "^5.6.2",
@ -154,7 +158,8 @@
"resolutions": {
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch"
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"openai@npm:^4.77.0": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch"
},
"packageManager": "yarn@4.5.0"
}

View File

@ -2,6 +2,7 @@ export const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
export const videoExts = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
export const audioExts = ['.mp3', '.wav', '.ogg', '.flac', '.aac']
export const documentExts = ['.pdf', '.docx', '.pptx', '.xlsx', '.odt', '.odp', '.ods']
export const bookExts = ['.epub']
export const textExts = [
'.txt', // 普通文本文件
'.md', // Markdown 文件

View File

@ -0,0 +1,6 @@
export type LoaderReturn = {
entriesAdded: number
uniqueId: string
uniqueIds: string[]
loaderType: string
}

104
scripts/check-i18n.js Normal file
View File

@ -0,0 +1,104 @@
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
var fs = require('fs')
var path = require('path')
var translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
var baseLocale = 'zh-CN'
var baseFileName = ''.concat(baseLocale, '.json')
var baseFilePath = path.join(translationsDir, baseFileName)
/**
* 递归同步 target 对象使其与 template 对象保持一致
* 1. 如果 template 中存在 target 中缺少的 key则添加'[to be translated]'
* 2. 如果 target 中存在 template 中不存在的 key则删除
* 3. 对于子对象递归同步
*
* @param target 目标对象需要更新的语言对象
* @param template 主模板对象中文
* @returns 返回是否对 target 进行了更新
*/
function syncRecursively(target, template) {
var isUpdated = false
// 添加 template 中存在但 target 中缺少的 key
for (var key in template) {
if (!(key in target)) {
target[key] =
typeof template[key] === 'object' && template[key] !== null ? {} : '[to be translated]:'.concat(template[key])
console.log('\u6DFB\u52A0\u65B0\u5C5E\u6027\uFF1A'.concat(key))
isUpdated = true
}
if (typeof template[key] === 'object' && template[key] !== null) {
if (typeof target[key] !== 'object' || target[key] === null) {
target[key] = {}
isUpdated = true
}
// 递归同步子对象
var childUpdated = syncRecursively(target[key], template[key])
if (childUpdated) {
isUpdated = true
}
}
}
// 删除 target 中存在但 template 中没有的 key
for (var targetKey in target) {
if (!(targetKey in template)) {
console.log('\u79FB\u9664\u591A\u4F59\u5C5E\u6027\uFF1A'.concat(targetKey))
delete target[targetKey]
isUpdated = true
}
}
return isUpdated
}
function syncTranslations() {
if (!fs.existsSync(baseFilePath)) {
console.error(
'\u4E3B\u6A21\u677F\u6587\u4EF6 '.concat(
baseFileName,
' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u8DEF\u5F84\u6216\u6587\u4EF6\u540D\u3002'
)
)
return
}
var baseContent = fs.readFileSync(baseFilePath, 'utf-8')
var baseJson = {}
try {
baseJson = JSON.parse(baseContent)
} catch (error) {
console.error('\u89E3\u6790 '.concat(baseFileName, ' \u51FA\u9519:'), error)
return
}
var files = fs.readdirSync(translationsDir).filter(function (file) {
return file.endsWith('.json') && file !== baseFileName
})
for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
var file = files_1[_i]
var filePath = path.join(translationsDir, file)
var targetJson = {}
try {
var fileContent = fs.readFileSync(filePath, 'utf-8')
targetJson = JSON.parse(fileContent)
} catch (error) {
console.error(
'\u89E3\u6790 '.concat(
file,
' \u51FA\u9519\uFF0C\u8DF3\u8FC7\u6B64\u6587\u4EF6\u3002\u9519\u8BEF\u4FE1\u606F:'
),
error
)
continue
}
var isUpdated = syncRecursively(targetJson, baseJson)
if (isUpdated) {
try {
fs.writeFileSync(filePath, JSON.stringify(targetJson, null, 2), 'utf-8')
console.log(
'\u6587\u4EF6 '.concat(file, ' \u5DF2\u66F4\u65B0\u540C\u6B65\u4E3B\u6A21\u677F\u7684\u5185\u5BB9\u3002')
)
} catch (error) {
console.error('\u5199\u5165 '.concat(file, ' \u51FA\u9519:'), error)
}
} else {
console.log('\u6587\u4EF6 '.concat(file, ' \u65E0\u9700\u66F4\u65B0\u3002'))
}
}
}
syncTranslations()

98
scripts/check-i18n.ts Normal file
View File

@ -0,0 +1,98 @@
import * as fs from 'fs'
import * as path from 'path'
const translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
const baseLocale = 'zh-CN'
const baseFileName = `${baseLocale}.json`
const baseFilePath = path.join(translationsDir, baseFileName)
/**
* target 使 template
* 1. template target key'[to be translated]'
* 2. target template key
* 3.
*
* @param target
* @param template
* @returns target
*/
function syncRecursively(target: any, template: any): boolean {
let isUpdated = false
// 添加 template 中存在但 target 中缺少的 key
for (const key in template) {
if (!(key in target)) {
target[key] =
typeof template[key] === 'object' && template[key] !== null ? {} : `[to be translated]:${template[key]}`
console.log(`添加新属性:${key}`)
isUpdated = true
}
if (typeof template[key] === 'object' && template[key] !== null) {
if (typeof target[key] !== 'object' || target[key] === null) {
target[key] = {}
isUpdated = true
}
// 递归同步子对象
const childUpdated = syncRecursively(target[key], template[key])
if (childUpdated) {
isUpdated = true
}
}
}
// 删除 target 中存在但 template 中没有的 key
for (const targetKey in target) {
if (!(targetKey in template)) {
console.log(`移除多余属性:${targetKey}`)
delete target[targetKey]
isUpdated = true
}
}
return isUpdated
}
function syncTranslations() {
if (!fs.existsSync(baseFilePath)) {
console.error(`主模板文件 ${baseFileName} 不存在,请检查路径或文件名。`)
return
}
const baseContent = fs.readFileSync(baseFilePath, 'utf-8')
let baseJson: Record<string, any> = {}
try {
baseJson = JSON.parse(baseContent)
} catch (error) {
console.error(`解析 ${baseFileName} 出错:`, error)
return
}
const files = fs.readdirSync(translationsDir).filter((file) => file.endsWith('.json') && file !== baseFileName)
for (const file of files) {
const filePath = path.join(translationsDir, file)
let targetJson: Record<string, any> = {}
try {
const fileContent = fs.readFileSync(filePath, 'utf-8')
targetJson = JSON.parse(fileContent)
} catch (error) {
console.error(`解析 ${file} 出错,跳过此文件。错误信息:`, error)
continue
}
const isUpdated = syncRecursively(targetJson, baseJson)
if (isUpdated) {
try {
fs.writeFileSync(filePath, JSON.stringify(targetJson, null, 2), 'utf-8')
console.log(`文件 ${file} 已更新同步主模板的内容。`)
} catch (error) {
console.error(`写入 ${file} 出错:`, error)
}
} else {
console.log(`文件 ${file} 无需更新。`)
}
}
}
syncTranslations()

View File

@ -0,0 +1,228 @@
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
import { BaseLoader } from '@llm-tools/embedjs-interfaces'
import { cleanString } from '@llm-tools/embedjs-utils'
import Logger from 'electron-log'
import EPub from 'epub'
import * as fs from 'fs'
/**
* epub
*/
interface EpubLoaderOptions {
/** epub 文件路径 */
filePath: string
/** 文本分块大小 */
chunkSize: number
/** 分块重叠大小 */
chunkOverlap: number
}
/**
* epub
*/
interface EpubMetadata {
/** 作者显示名称(例如:"Lewis Carroll" */
creator?: string
/** 作者规范化名称,用于排序和索引(例如:"Carroll, Lewis" */
creatorFileAs?: string
/** 书籍标题(例如:"Alice's Adventures in Wonderland" */
title?: string
/** 语言代码(例如:"en" 或 "zh-CN" */
language?: string
/** 主题或分类(例如:"Fantasy"、"Fiction" */
subject?: string
/** 创建日期(例如:"2024-02-14" */
date?: string
/** 书籍描述或简介 */
description?: string
}
/**
* epub
*/
interface EpubChapter {
/** 章节 ID */
id: string
/** 章节标题 */
title?: string
/** 章节顺序 */
order?: number
}
/**
* epub
* epub
*/
export class EpubLoader extends BaseLoader<Record<string, string | number | boolean>, Record<string, unknown>> {
protected filePath: string
protected chunkSize: number
protected chunkOverlap: number
private extractedText: string
private metadata: EpubMetadata | null
/**
* epub
* @param options
*/
constructor(options: EpubLoaderOptions) {
super(options.filePath, {
chunkSize: options.chunkSize,
chunkOverlap: options.chunkOverlap
})
this.filePath = options.filePath
this.chunkSize = options.chunkSize
this.chunkOverlap = options.chunkOverlap
this.extractedText = ''
this.metadata = null
}
/**
* epub
* epub 使 'end' 访
* @param epub epub
* @returns
*/
private waitForEpubInit(epub: any): Promise<{ metadata: EpubMetadata; chapters: EpubChapter[] }> {
return new Promise((resolve, reject) => {
epub.on('end', () => {
// 提取元数据
const metadata: EpubMetadata = {
creator: epub.metadata.creator,
creatorFileAs: epub.metadata.creatorFileAs,
title: epub.metadata.title,
language: epub.metadata.language,
subject: epub.metadata.subject,
date: epub.metadata.date,
description: epub.metadata.description
}
// 提取章节信息
const chapters: EpubChapter[] = epub.flow.map((chapter: any, index: number) => ({
id: chapter.id,
title: chapter.title || `Chapter ${index + 1}`,
order: index + 1
}))
resolve({ metadata, chapters })
})
epub.on('error', (error: Error) => {
reject(error)
})
epub.parse()
})
}
/**
*
* @param epub epub
* @param chapterId ID
* @returns
*/
private getChapter(epub: any, chapterId: string): Promise<string> {
return new Promise((resolve, reject) => {
epub.getChapter(chapterId, (error: Error | null, text: string) => {
if (error) {
reject(error)
} else {
resolve(text)
}
})
})
}
/**
* epub
* 1.
* 2. epub
* 3.
* 4. HTML
* 5.
*/
private async extractTextFromEpub() {
try {
// 检查文件是否存在
if (!fs.existsSync(this.filePath)) {
throw new Error(`File not found: ${this.filePath}`)
}
const epub = new EPub(this.filePath)
// 等待 epub 初始化完成并获取元数据
const { metadata, chapters } = await this.waitForEpubInit(epub)
this.metadata = metadata
if (!epub.flow || epub.flow.length === 0) {
throw new Error('No content found in epub file')
}
const chapterTexts: string[] = []
// 遍历所有章节
for (const chapter of chapters) {
try {
const content = await this.getChapter(epub, chapter.id)
if (!content) {
continue
}
// 移除 HTML 标签并清理文本
const text = content
.replace(/<[^>]*>/g, ' ') // 移除所有 HTML 标签
.replace(/\s+/g, ' ') // 将多个空白字符替换为单个空格
.trim() // 移除首尾空白
if (text) {
chapterTexts.push(text)
}
} catch (error) {
Logger.error(`[EpubLoader] Error processing chapter ${chapter.id}:`, error)
}
}
// 使用双换行符连接所有章节文本
this.extractedText = chapterTexts.join('\n\n')
} catch (error) {
Logger.error('[EpubLoader] Error in extractTextFromEpub:', error)
throw error
}
}
/**
*
* BaseLoader
*
*/
override async *getUnfilteredChunks() {
// 如果还没有提取文本,先提取
if (!this.extractedText) {
await this.extractTextFromEpub()
}
Logger.info('[EpubLoader] 书名:', this.metadata?.title || '未知书名', ' 文本大小:', this.extractedText.length)
// 创建文本分块器
const chunker = new RecursiveCharacterTextSplitter({
chunkSize: this.chunkSize,
chunkOverlap: this.chunkOverlap
})
// 清理并分割文本
const chunks = await chunker.splitText(cleanString(this.extractedText))
// 为每个文本块添加元数据
for (const chunk of chunks) {
yield {
pageContent: chunk,
metadata: {
source: this.filePath,
title: this.metadata?.title || '',
creator: this.metadata?.creator || '',
language: this.metadata?.language || ''
}
}
}
}
}

125
src/main/loader/index.ts Normal file
View File

@ -0,0 +1,125 @@
import * as fs from 'node:fs'
import { LocalPathLoader, RAGApplication, TextLoader } from '@llm-tools/embedjs'
import type { AddLoaderReturn } from '@llm-tools/embedjs-interfaces'
import { WebLoader } from '@llm-tools/embedjs-loader-web'
import { LoaderReturn } from '@shared/config/types'
import { FileType, KnowledgeBaseParams } from '@types'
import Logger from 'electron-log'
import { EpubLoader } from './epubLoader'
import { OdLoader, OdType } from './odLoader'
// embedjs内置loader类型
const commonExts = ['.pdf', '.csv', '.json', '.docx', '.pptx', '.xlsx', '.md']
export async function addOdLoader(
ragApplication: RAGApplication,
file: FileType,
base: KnowledgeBaseParams,
forceReload: boolean
): Promise<AddLoaderReturn> {
const loaderMap: Record<string, OdType> = {
'.odt': OdType.OdtLoader,
'.ods': OdType.OdsLoader,
'.odp': OdType.OdpLoader
}
const odType = loaderMap[file.ext]
if (!odType) {
throw new Error('Unknown odType')
}
return ragApplication.addLoader(
new OdLoader({
odType,
filePath: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
export async function addFileLoader(
ragApplication: RAGApplication,
file: FileType,
base: KnowledgeBaseParams,
forceReload: boolean
): Promise<LoaderReturn> {
// 内置类型
if (commonExts.includes(file.ext)) {
const loaderReturn = await ragApplication.addLoader(
// @ts-ignore LocalPathLoader
new LocalPathLoader({ path: file.path, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }) as any,
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
// 自定义类型
if (['.odt', '.ods', '.odp'].includes(file.ext)) {
const loaderReturn = await addOdLoader(ragApplication, file, base, forceReload)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
// epub 文件处理
if (file.ext === '.epub') {
const loaderReturn = await ragApplication.addLoader(
new EpubLoader({
filePath: file.path,
chunkSize: base.chunkSize ?? 1000,
chunkOverlap: base.chunkOverlap ?? 200
}) as any,
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
const fileContent = fs.readFileSync(file.path, 'utf-8')
// HTML类型
if (['.html', '.htm'].includes(file.ext)) {
const loaderReturn = await ragApplication.addLoader(
new WebLoader({
urlOrContent: fileContent,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
}
}
// 文本类型
const loaderReturn = await ragApplication.addLoader(
new TextLoader({ text: fileContent, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }) as any,
forceReload
)
Logger.info('[KnowledgeBase] processing file', file.path)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}

View File

@ -0,0 +1,71 @@
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
import { BaseLoader } from '@llm-tools/embedjs-interfaces'
import { cleanString } from '@llm-tools/embedjs-utils'
import md5 from 'md5'
import { OfficeParserConfig, parseOfficeAsync } from 'officeparser'
export enum OdType {
OdtLoader = 'OdtLoader',
OdsLoader = 'OdsLoader',
OdpLoader = 'OdpLoader',
undefined = 'undefined'
}
export class OdLoader<OdType> extends BaseLoader<{ type: string }> {
private readonly odType: OdType
private readonly filePath: string
private extractedText: string
private config: OfficeParserConfig
constructor({
odType,
filePath,
chunkSize,
chunkOverlap
}: {
odType: OdType
filePath: string
chunkSize?: number
chunkOverlap?: number
}) {
super(`${odType}_${md5(filePath)}`, { filePath }, chunkSize ?? 1000, chunkOverlap ?? 0)
this.odType = odType
this.filePath = filePath
this.extractedText = ''
this.config = {
newlineDelimiter: ' ',
ignoreNotes: false
}
}
private async extractTextFromOdt() {
try {
this.extractedText = await parseOfficeAsync(this.filePath, this.config)
} catch (err) {
console.error('odLoader error', err)
throw err
}
}
override async *getUnfilteredChunks() {
if (!this.extractedText) {
await this.extractTextFromOdt()
}
const chunker = new RecursiveCharacterTextSplitter({
chunkSize: this.chunkSize,
chunkOverlap: this.chunkOverlap
})
const chunks = await chunker.splitText(cleanString(this.extractedText))
for (const chunk of chunks) {
yield {
pageContent: chunk,
metadata: {
type: this.odType as string,
source: this.filePath
}
}
}
}
}

View File

@ -1,18 +1,19 @@
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 { RAGApplication, RAGApplicationBuilder, TextLoader } from '@llm-tools/embedjs'
import type { 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 { addFileLoader } from '@main/loader'
import { getInstanceName } from '@main/utils'
import { getAllFiles } from '@main/utils/file'
import type { LoaderReturn } from '@shared/config/types'
import { FileType, KnowledgeBaseParams, KnowledgeItem } from '@types'
import { app } from 'electron'
import { v4 as uuidv4 } from 'uuid'
class KnowledgeService {
private storageDir = path.join(app.getPath('userData'), 'Data', 'KnowledgeBase')
@ -79,153 +80,88 @@ class KnowledgeService {
public add = async (
_: Electron.IpcMainInvokeEvent,
{ base, item, forceReload = false }: { base: KnowledgeBaseParams; item: KnowledgeItem; forceReload: boolean }
): Promise<AddLoaderReturn> => {
): Promise<LoaderReturn> => {
const ragApplication = await this.getRagApplication(base)
if (item.type === 'directory') {
const directory = item.content as string
return await ragApplication.addLoader(
new LocalPathLoader({ path: directory, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }) as any,
forceReload
)
const files = getAllFiles(directory)
const loaderPromises = files.map((file) => addFileLoader(ragApplication, file, base, forceReload))
const loaderResults = await Promise.all(loaderPromises)
const uniqueIds = loaderResults.map((result) => result.uniqueId)
return {
entriesAdded: loaderResults.length,
uniqueId: `DirectoryLoader_${uuidv4()}`,
uniqueIds,
loaderType: 'DirectoryLoader'
} as LoaderReturn
}
if (item.type === 'url') {
const content = item.content as string
if (content.startsWith('http')) {
return await ragApplication.addLoader(
const loaderReturn = await ragApplication.addLoader(
new WebLoader({ urlOrContent: content, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }) as any,
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
}
if (item.type === 'sitemap') {
const content = item.content as string
// @ts-ignore loader type
return await ragApplication.addLoader(
const loaderReturn = await ragApplication.addLoader(
new SitemapLoader({ url: content, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }) as any,
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
if (item.type === 'note') {
const content = item.content as string
console.debug('chunkSize', base.chunkSize)
return await ragApplication.addLoader(
const loaderReturn = await ragApplication.addLoader(
new TextLoader({ text: content, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }),
forceReload
)
return {
entriesAdded: loaderReturn.entriesAdded,
uniqueId: loaderReturn.uniqueId,
uniqueIds: [loaderReturn.uniqueId],
loaderType: loaderReturn.loaderType
} as LoaderReturn
}
if (item.type === 'file') {
const file = item.content as FileType
console.log(file)
if (file.ext === '.pdf') {
return await ragApplication.addLoader(
new PdfLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
if (file.ext === '.docx') {
return await ragApplication.addLoader(
new DocxLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
if (file.ext === '.pptx') {
return await ragApplication.addLoader(
new PptLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
if (file.ext === '.xlsx') {
return await ragApplication.addLoader(
new ExcelLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
if (file.ext === '.csv') {
try {
const module = await import('@llm-tools/embedjs-loader-csv/src/index.js')
// 使用module中的功能
console.log(module.CsvLoader) // 假设模块导出了一个默认值
return await ragApplication.addLoader(
new module.CsvLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap,
csvParseOptions: {
columns: true
}
}) as any,
forceReload
)
} catch (error) {
console.error('加载模块时出错:', error)
}
}
if (['.md'].includes(file.ext)) {
return await ragApplication.addLoader(
new MarkdownLoader({
filePathOrUrl: file.path,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
const fileContent = fs.readFileSync(file.path, 'utf-8')
if (['.html'].includes(file.ext)) {
return await ragApplication.addLoader(
new WebLoader({
urlOrContent: fileContent,
chunkSize: base.chunkSize,
chunkOverlap: base.chunkOverlap
}) as any,
forceReload
)
}
return await ragApplication.addLoader(
new TextLoader({ text: fileContent, chunkSize: base.chunkSize, chunkOverlap: base.chunkOverlap }),
forceReload
)
return await addFileLoader(ragApplication, file, base, forceReload)
}
return { entriesAdded: 0, uniqueId: '', loaderType: '' }
return { entriesAdded: 0, uniqueId: '', uniqueIds: [''], loaderType: '' }
}
public remove = async (
_: Electron.IpcMainInvokeEvent,
{ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }
{ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }
): Promise<void> => {
const ragApplication = await this.getRagApplication(base)
await ragApplication.deleteLoader(uniqueId)
console.debug(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`)
for (const id of uniqueIds) {
await ragApplication.deleteLoader(id)
}
}
public search = async (

View File

@ -22,7 +22,11 @@ function getShortcutHandler(shortcut: Shortcut) {
case 'show_app':
return (window: BrowserWindow) => {
if (window.isVisible()) {
window.hide()
if (window.isFocused()) {
window.hide()
} else {
window.focus()
}
} else {
window.show()
window.focus()
@ -52,6 +56,59 @@ function handleZoom(delta: number) {
}
}
const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat = (
shortcut: string | string[]
): string => {
const accelerator = (() => {
if (Array.isArray(shortcut)) {
return shortcut
} else {
return shortcut.split('+').map((key) => key.trim())
}
})()
return accelerator
.map((key) => {
switch (key) {
case 'Control':
return 'CommandOrControl'
case 'Ctrl':
return 'CommandOrControl'
case 'ArrowUp':
return 'Up'
case 'ArrowDown':
return 'Down'
case 'ArrowLeft':
return 'Left'
case 'ArrowRight':
return 'Right'
case 'AltGraph':
return 'Alt'
case 'Slash':
return '/'
case 'Semicolon':
return ';'
case 'BracketLeft':
return '['
case 'BracketRight':
return ']'
case 'Backslash':
return '\\'
case 'Quote':
return "'"
case 'Comma':
return ','
case 'Minus':
return '-'
case 'Equal':
return '='
default:
return key
}
})
.join('+')
}
export function registerShortcuts(window: BrowserWindow) {
window.webContents.setZoomFactor(configManager.getZoomFactor())
@ -75,11 +132,11 @@ export function registerShortcuts(window: BrowserWindow) {
const accelerator = formatShortcutKey(shortcut.shortcut)
if (shortcut.key === 'show_app') {
if (shortcut.key === 'show_app' && shortcut.enabled) {
showAppAccelerator = accelerator
}
if (shortcut.key === 'mini_window') {
if (shortcut.key === 'mini_window' && shortcut.enabled) {
showMiniWindowAccelerator = accelerator
}
@ -100,7 +157,10 @@ export function registerShortcuts(window: BrowserWindow) {
}
if (shortcut.enabled) {
globalShortcut.register(formatShortcutKey(shortcut.shortcut), () => handler(window))
const accelerator = convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat(
shortcut.shortcut
)
globalShortcut.register(accelerator, () => handler(window))
}
} catch (error) {
Logger.error(`[ShortcutService] Failed to register shortcut ${shortcut.key}`)
@ -116,12 +176,16 @@ export function registerShortcuts(window: BrowserWindow) {
if (showAppAccelerator) {
const handler = getShortcutHandler({ key: 'show_app' } as Shortcut)
handler && globalShortcut.register(showAppAccelerator, () => handler(window))
const accelerator =
convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat(showAppAccelerator)
handler && globalShortcut.register(accelerator, () => handler(window))
}
if (showMiniWindowAccelerator) {
const handler = getShortcutHandler({ key: 'mini_window' } as Shortcut)
handler && globalShortcut.register(showMiniWindowAccelerator, () => handler(window))
const accelerator =
convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat(showMiniWindowAccelerator)
handler && globalShortcut.register(accelerator, () => handler(window))
}
} catch (error) {
Logger.error('[ShortcutService] Failed to unregister shortcuts')

View File

@ -167,7 +167,7 @@ export class WindowService {
const oauthProviderUrls = [
'https://account.siliconflow.cn/oauth',
'https://cloud.siliconflow.cn/expensebill',
'https://aihubmix.com/oauth',
'https://aihubmix.com/token',
'https://aihubmix.com/topup'
]

View File

@ -1,5 +1,9 @@
import * as fs from 'node:fs'
import path from 'node:path'
import { audioExts, documentExts, imageExts, textExts, videoExts } from '@shared/config/constant'
import { FileTypes } from '@types'
import { FileType, FileTypes } from '@types'
import { v4 as uuidv4 } from 'uuid'
export function getFileType(ext: string): FileTypes {
ext = ext.toLowerCase()
@ -10,3 +14,39 @@ export function getFileType(ext: string): FileTypes {
if (documentExts.includes(ext)) return FileTypes.DOCUMENT
return FileTypes.OTHER
}
export function getAllFiles(dirPath: string, arrayOfFiles: FileType[] = []): FileType[] {
const files = fs.readdirSync(dirPath)
files.forEach((file) => {
const fullPath = path.join(dirPath, file)
if (fs.statSync(fullPath).isDirectory()) {
arrayOfFiles = getAllFiles(fullPath, arrayOfFiles)
} else {
const ext = path.extname(file)
const fileType = getFileType(ext)
if ([FileTypes.OTHER, FileTypes.IMAGE, FileTypes.VIDEO, FileTypes.AUDIO].includes(fileType)) {
return
}
const name = path.basename(file)
const size = fs.statSync(fullPath).size
const fileItem: FileType = {
id: uuidv4(),
name,
path: fullPath,
size,
ext,
count: 1,
origin_name: name,
type: fileType,
created_at: new Date()
}
arrayOfFiles.push(fileItem)
}
})
return arrayOfFiles
}

View File

@ -1,9 +1,10 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import type { FileMetadataResponse, ListFilesResponse, UploadFileResponse } from '@google/generative-ai/server'
import { AddLoaderReturn, ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { FileType } from '@renderer/types'
import { WebDavConfig } from '@renderer/types'
import { AppInfo, KnowledgeBaseParams, KnowledgeItem, LanguageVarious } from '@renderer/types'
import type { LoaderReturn } from '@shared/config/types'
import type { OpenDialogOptions } from 'electron'
import type { UpdateInfo } from 'electron-updater'
import { Readable } from 'stream'
@ -78,8 +79,16 @@ declare global {
base: KnowledgeBaseParams
item: KnowledgeItem
forceReload?: boolean
}) => Promise<AddLoaderReturn>
remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise<void>
}) => Promise<LoaderReturn>
remove: ({
uniqueId,
uniqueIds,
base
}: {
uniqueId: string
uniqueIds: string[]
base: KnowledgeBaseParams
}) => Promise<void>
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise<ExtractChunkData[]>
}
window: {

View File

@ -71,8 +71,8 @@ const api = {
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 }),
remove: ({ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }) =>
ipcRenderer.invoke('knowledge-base:remove', { uniqueId, uniqueIds, base }),
search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) =>
ipcRenderer.invoke('knowledge-base:search', { search, base })
},

View File

@ -0,0 +1,27 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" rx="32" fill="#0057CE"/>
<mask id="path-2-inside-1_4113_89308" fill="white">
<path d="M169.6 131.626C173.075 129.641 176.32 128.241 180.1 126.943C183.74 125.695 187.444 124.664 191.186 123.735C194.915 122.806 198.682 122.017 202.449 121.228C206.216 120.439 209.958 119.675 213.598 118.314C231.429 111.619 242.221 93.6357 239.612 74.9396C237.003 56.2435 221.692 41.8237 202.691 40.1564C194.062 39.4055 185.726 41.4164 178.013 44.9418C170.326 48.4545 163.288 53.4435 157.166 59.158C144.795 70.676 135.657 85.4649 130.083 101.208C124.47 117.054 122.37 134.095 123.694 150.806C124.356 159.129 125.883 167.504 128.326 175.509C130.719 183.362 134.181 191.469 138.839 198.342C136.828 185.475 138.559 172.175 143.917 160.262C149.262 148.375 158.121 138.193 169.6 131.626Z"/>
</mask>
<path d="M169.6 131.626C173.075 129.641 176.32 128.241 180.1 126.943C183.74 125.695 187.444 124.664 191.186 123.735C194.915 122.806 198.682 122.017 202.449 121.228C206.216 120.439 209.958 119.675 213.598 118.314C231.429 111.619 242.221 93.6357 239.612 74.9396C237.003 56.2435 221.692 41.8237 202.691 40.1564C194.062 39.4055 185.726 41.4164 178.013 44.9418C170.326 48.4545 163.288 53.4435 157.166 59.158C144.795 70.676 135.657 85.4649 130.083 101.208C124.47 117.054 122.37 134.095 123.694 150.806C124.356 159.129 125.883 167.504 128.326 175.509C130.719 183.362 134.181 191.469 138.839 198.342C136.828 185.475 138.559 172.175 143.917 160.262C149.262 148.375 158.121 138.193 169.6 131.626Z" fill="white" stroke="white" stroke-width="32" mask="url(#path-2-inside-1_4113_89308)"/>
<path d="M162.246 150.4C161.915 153.913 163.073 157.464 165.542 160.06C168.011 162.657 171.499 164.031 174.668 165.253C178.13 166.577 181.12 167.658 184.353 169.529C187.433 171.311 190.157 173.526 192.435 176.262C201.802 187.449 200.937 203.867 190.462 214.049C179.988 224.23 163.379 224.778 152.243 215.321C149.404 212.903 146.884 209.798 144.81 206.756C141.654 186.52 147.775 165.317 162.246 150.4Z" fill="white"/>
<mask id="path-4-outside-2_4113_89308" maskUnits="userSpaceOnUse" x="136" y="138.4" width="71" height="92" fill="black">
<rect fill="white" x="136" y="138.4" width="71" height="92"/>
<path d="M162.246 150.4C165.542 153.666 163.073 157.464 165.542 160.06C168.011 162.657 171.499 164.031 174.668 165.253C178.13 166.577 181.12 167.658 184.353 169.529C187.433 171.311 190.157 173.526 192.435 176.262C201.802 187.449 200.937 203.867 190.462 214.049C179.988 224.23 163.379 224.778 152.243 215.321C149.404 212.903 146.884 209.798 144.81 206.756C141.654 186.52 147.775 165.317 162.246 150.4Z"/>
</mask>
<path d="M162.246 150.4C165.542 153.666 163.073 157.464 165.542 160.06C168.011 162.657 171.499 164.031 174.668 165.253C178.13 166.577 181.12 167.658 184.353 169.529C187.433 171.311 190.157 173.526 192.435 176.262C201.802 187.449 200.937 203.867 190.462 214.049C179.988 224.23 163.379 224.778 152.243 215.321C149.404 212.903 146.884 209.798 144.81 206.756C141.654 186.52 147.775 165.317 162.246 150.4Z" stroke="#0057CE" stroke-width="16" mask="url(#path-4-outside-2_4113_89308)"/>
<mask id="path-5-inside-3_4113_89308" fill="white">
<path d="M50.4113 61.9063C63.3547 61.8935 75.9164 69.008 85.0163 76.9879C94.6761 85.4641 102.16 96.2567 107.085 107.991C112.036 119.789 114.416 132.542 114.327 145.282C114.238 157.665 111.769 171.079 106.296 182.394C105.774 167.821 100.123 153.885 90.3107 143.003C88.5926 141.107 86.7981 139.389 84.6599 137.938C82.5218 136.487 80.2691 135.418 77.8382 134.565C73.1164 132.911 67.7838 132.134 62.8711 131.6C57.8057 131.04 52.7149 130.709 47.6622 129.971C42.4695 129.207 37.8114 128.087 33.1787 125.427C19.688 117.715 13.1463 102.009 17.1808 87.1441C21.2153 72.2661 34.846 61.919 50.4113 61.9063Z"/>
</mask>
<path d="M50.4113 61.9063C63.3547 61.8935 75.9164 69.008 85.0163 76.9879C94.6761 85.4641 102.16 96.2567 107.085 107.991C112.036 119.789 114.416 132.542 114.327 145.282C114.238 157.665 111.769 171.079 106.296 182.394C105.774 167.821 100.123 153.885 90.3107 143.003C88.5926 141.107 86.7981 139.389 84.6599 137.938C82.5218 136.487 80.2691 135.418 77.8382 134.565C73.1164 132.911 67.7838 132.134 62.8711 131.6C57.8057 131.04 52.7149 130.709 47.6622 129.971C42.4695 129.207 37.8114 128.087 33.1787 125.427C19.688 117.715 13.1463 102.009 17.1808 87.1441C21.2153 72.2661 34.846 61.919 50.4113 61.9063Z" fill="white" stroke="white" stroke-width="32" mask="url(#path-5-inside-3_4113_89308)"/>
<mask id="path-6-inside-4_4113_89308" fill="white">
<path d="M82.5802 149.38C81.3584 148.03 80.0857 146.745 78.673 145.6C80.4294 148.578 80.6075 151.95 79.8694 155.196C79.1312 158.429 77.5021 161.419 75.4403 163.99C73.3149 166.625 70.8204 168.725 68.1095 170.71C65.7423 172.441 62.2932 174.656 60.1551 176.73C53.8679 182.839 52.5824 192.384 57.0369 199.893C61.4914 207.415 70.5277 210.979 78.9912 208.535C83.662 207.186 87.6202 204.144 90.7638 200.67C93.9455 197.157 96.5291 192.983 98.5655 188.757C98.0437 174.185 92.3928 160.261 82.5802 149.38Z"/>
</mask>
<path d="M82.5802 149.38C81.3584 148.03 80.0857 146.745 78.673 145.6C80.4294 148.578 80.6075 151.95 79.8694 155.196C79.1312 158.429 77.5021 161.419 75.4403 163.99C73.3149 166.625 70.8204 168.725 68.1095 170.71C65.7423 172.441 62.2932 174.656 60.1551 176.73C53.8679 182.839 52.5824 192.384 57.0369 199.893C61.4914 207.415 70.5277 210.979 78.9912 208.535C83.662 207.186 87.6202 204.144 90.7638 200.67C93.9455 197.157 96.5291 192.983 98.5655 188.757C98.0437 174.185 92.3928 160.261 82.5802 149.38Z" stroke="white" stroke-width="24" mask="url(#path-6-inside-4_4113_89308)"/>
<mask id="path-7-outside-5_4113_89308" maskUnits="userSpaceOnUse" x="45.3994" y="138.6" width="62" height="79" fill="black">
<rect fill="white" x="45.3994" y="138.6" width="62" height="79"/>
<path d="M82.5802 149.38C81.3584 148.03 80.0857 146.745 78.673 145.6C80.4294 148.578 80.6075 151.95 79.8694 155.196C79.1312 158.429 77.5021 161.419 75.4403 163.99C73.3149 166.625 70.8204 168.725 68.1095 170.71C65.7423 172.441 62.2932 174.656 60.1551 176.73C53.8679 182.839 52.5824 192.384 57.0369 199.893C61.4914 207.415 70.5277 210.979 78.9912 208.535C83.662 207.186 87.6202 204.144 90.7638 200.67C93.9455 197.157 96.5291 192.983 98.5655 188.757C98.0437 174.185 92.3928 160.261 82.5802 149.38Z"/>
</mask>
<path d="M82.5802 149.38C81.3584 148.03 80.0857 146.745 78.673 145.6C80.4294 148.578 80.6075 151.95 79.8694 155.196C79.1312 158.429 77.5021 161.419 75.4403 163.99C73.3149 166.625 70.8204 168.725 68.1095 170.71C65.7423 172.441 62.2932 174.656 60.1551 176.73C53.8679 182.839 52.5824 192.384 57.0369 199.893C61.4914 207.415 70.5277 210.979 78.9912 208.535C83.662 207.186 87.6202 204.144 90.7638 200.67C93.9455 197.157 96.5291 192.983 98.5655 188.757C98.0437 174.185 92.3928 160.261 82.5802 149.38Z" fill="white"/>
<path d="M82.5802 149.38C81.3584 148.03 80.0857 146.745 78.673 145.6C80.4294 148.578 80.6075 151.95 79.8694 155.196C79.1312 158.429 77.5021 161.419 75.4403 163.99C73.3149 166.625 70.8204 168.725 68.1095 170.71C65.7423 172.441 62.2932 174.656 60.1551 176.73C53.8679 182.839 52.5824 192.384 57.0369 199.893C61.4914 207.415 70.5277 210.979 78.9912 208.535C83.662 207.186 87.6202 204.144 90.7638 200.67C93.9455 197.157 96.5291 192.983 98.5655 188.757C98.0437 174.185 92.3928 160.261 82.5802 149.38Z" stroke="#0057CE" stroke-width="16" mask="url(#path-7-outside-5_4113_89308)"/>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512px" height="512px" viewBox="0 0 512 512" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(85.09804%,85.09804%,85.09804%);fill-opacity:1;" d="M 512 256 C 512 114.613281 397.386719 0 256 0 C 114.613281 0 0 114.613281 0 256 C 0 397.386719 114.613281 512 256 512 C 397.386719 512 512 397.386719 512 256 Z M 512 256 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 256.011719 114.753906 C 167.050781 114.753906 94.945312 186.261719 94.945312 274.507812 L 94.945312 350.988281 L 124.628906 350.988281 L 124.628906 343.359375 C 124.628906 307.574219 153.867188 278.558594 189.941406 278.558594 C 226.015625 278.558594 255.253906 307.585938 255.253906 343.359375 L 255.253906 350.988281 L 284.9375 350.988281 L 284.9375 343.359375 C 284.9375 291.308594 242.390625 249.140625 189.929688 249.140625 C 169.503906 249.140625 150.582031 255.53125 135.082031 266.433594 C 151.296875 234.464844 184.691406 212.535156 223.242188 212.535156 C 277.707031 212.535156 321.867188 256.339844 321.867188 310.355469 L 321.867188 350.996094 L 351.5625 350.996094 L 351.5625 310.355469 C 351.5625 240.074219 294.113281 183.082031 223.242188 183.082031 C 191.382812 183.082031 162.230469 194.601562 139.785156 213.683594 C 161.824219 172.375 205.578125 144.214844 256 144.214844 C 328.566406 144.214844 387.382812 202.550781 387.382812 274.515625 L 387.382812 350.996094 L 417.066406 350.996094 L 417.066406 274.515625 C 417.066406 186.28125 344.960938 114.761719 256 114.761719 Z M 256.011719 114.753906 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -256,6 +256,10 @@ body,
border: 1px solid var(--color-background-mute);
}
}
.group-menu-bar {
margin-left: 0;
background-color: var(--color-background);
}
code {
color: var(--color-text);
}

View File

@ -0,0 +1,26 @@
import React from 'react'
import styled from 'styled-components'
type Props = {
text: string | number
maxLine?: number
} & React.HTMLAttributes<HTMLDivElement>
const Ellipsis = (props: Props) => {
const { text, maxLine = 1, ...rest } = props
return (
<EllipsisContainer maxLine={maxLine} {...rest}>
{text}
</EllipsisContainer>
)
}
const EllipsisContainer = styled.div<{ maxLine: number }>`
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
-webkit-line-clamp: ${({ maxLine }) => maxLine};
`
export default Ellipsis

View File

@ -257,5 +257,6 @@ export default class MinApp {
TopView.hide('MinApp')
store.dispatch(setMinappShow(false))
MinApp.app = null
MinApp.onClose = () => {}
}
}

View File

@ -4,7 +4,7 @@ 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 { FC, useState, useEffect } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import styled from 'styled-components'
@ -26,8 +26,22 @@ const MinAppsPopover: FC<Props> = ({ children }) => {
setOpen(false)
}
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 100);
useEffect(() => {
const handleResize = () => {
setMaxHeight(window.innerHeight - 100);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
const content = (
<PopoverContent>
<PopoverContent maxHeight={maxHeight}>
<AppsContainer>
{minapps.map((app) => (
<App key={app.id} app={app} onClick={handleClose} size={50} />
@ -54,12 +68,15 @@ const MinAppsPopover: FC<Props> = ({ children }) => {
)
}
const PopoverContent = styled(Scrollbar)``
const AppsContainer = styled.div`
display: grid;
grid-template-columns: repeat(6, minmax(90px, 1fr));
gap: 18px;
const PopoverContent = styled(Scrollbar)<{ maxHeight: number }>`
max-height: ${(props) => props.maxHeight}px;
overflow-y: auto;
`
const AppsContainer = styled.div`
display: grid;
grid-template-columns: repeat(6, minmax(90px, 1fr));
gap: 18px;
`;
export default MinAppsPopover

View File

@ -1,6 +1,12 @@
import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons'
import {
FileSearchOutlined,
FolderOutlined,
PictureOutlined,
QuestionCircleOutlined,
TranslationOutlined
} from '@ant-design/icons'
import { isMac } from '@renderer/config/constant'
import { isLocalAi, UserAvatar } from '@renderer/config/env'
import { AppLogo, isLocalAi, UserAvatar } from '@renderer/config/env'
import { useTheme } from '@renderer/context/ThemeProvider'
import useAvatar from '@renderer/hooks/useAvatar'
import { useMinapps } from '@renderer/hooks/useMinapps'
@ -42,6 +48,14 @@ const Sidebar: FC = () => {
navigate(path)
}
const onOpenDocs = () => {
MinApp.start({
name: t('docs.title'),
url: 'https://docs.cherry-ai.com/',
logo: AppLogo
})
}
return (
<Container
id="app-sidebar"
@ -64,6 +78,11 @@ const Sidebar: FC = () => {
)}
</MainMenusContainer>
<Menus onClick={MinApp.onClose}>
<Tooltip title={t('docs.title')} mouseEnterDelay={0.8} placement="right">
<Icon onClick={onOpenDocs}>
<QuestionCircleOutlined />
</Icon>
</Tooltip>
<Tooltip title={t('settings.theme.title')} mouseEnterDelay={0.8} placement="right">
<Icon onClick={() => toggleTheme()}>
{theme === 'dark' ? (

View File

@ -2,6 +2,7 @@ export const DEFAULT_TEMPERATURE = 1.0
export const DEFAULT_CONTEXTCOUNT = 5
export const DEFAULT_MAX_TOKENS = 4096
export const DEFAULT_KNOWLEDGE_DOCUMENT_COUNT = 6
export const DEFAULT_KNOWLEDGE_THRESHOLD = 0.0
export const FONT_FAMILY =
"Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"

View File

@ -226,6 +226,18 @@ export const EMBEDDING_MODELS = [
{
id: 'text-embedding-004',
max_context: 2048
},
{
id: 'deepset-mxbai-embed-de-large-v1',
max_context: 512
},
{
id: 'mxbai-embed-large-v1',
max_context: 512
},
{
id: 'mxbai-embed-2d-large-v1',
max_context: 512
}
]

View File

@ -1,4 +1,5 @@
import ThreeMinTopAppLogo from '@renderer/assets/images/apps/3mintop.png?url'
import AIStudioLogo from '@renderer/assets/images/apps/aistudio.svg?url'
import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png?url'
import BaicuanAppLogo from '@renderer/assets/images/apps/baixiaoying.webp?url'
import BoltAppLogo from '@renderer/assets/images/apps/bolt.svg?url'
@ -15,7 +16,8 @@ import HikaLogo from '@renderer/assets/images/apps/hika.webp?url'
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg?url'
import KimiAppLogo from '@renderer/assets/images/apps/kimi.jpg?url'
import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp?url'
import NamiAiSearchLogo from '@renderer/assets/images/apps/nm.webp?url'
import NamiAiLogo from '@renderer/assets/images/apps/nm.png?url'
import NamiAiSearchLogo from '@renderer/assets/images/apps/nm-search.webp?url'
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp?url'
import PoeAppLogo from '@renderer/assets/images/apps/poe.webp?url'
import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png?url'
@ -25,9 +27,12 @@ import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.png?url'
import ThinkAnyLogo from '@renderer/assets/images/apps/thinkany.webp?url'
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png?url'
import WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg?url'
import XiaoYiAppLogo from '@renderer/assets/images/apps/xiaoyi.webp?url'
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.png?url'
import NotebookLMAppLogo from '@renderer/assets/images/apps/notebooklm.svg?url'
import YuewenAppLogo from '@renderer/assets/images/apps/yuewen.png?url'
import ZhihuAppLogo from '@renderer/assets/images/apps/zhihu.png?url'
import CozeAppLogo from '@renderer/assets/images/apps/coze.webp?url'
import ClaudeAppLogo from '@renderer/assets/images/models/claude.png?url'
import HailuoModelLogo from '@renderer/assets/images/models/hailuo.png?url'
import QwenModelLogo from '@renderer/assets/images/models/qwen.png?url'
@ -219,6 +224,13 @@ export const DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'nm',
name: '纳米AI',
logo: NamiAiLogo,
url: 'https://bot.n.cn/',
bodered: true
},
{
id: 'nm-search',
name: '纳米AI搜索',
logo: NamiAiSearchLogo,
url: 'https://www.n.cn/',
@ -276,6 +288,32 @@ export const DEFAULT_MIN_APPS: MinAppType[] = [
logo: ThreeMinTopAppLogo,
url: 'https://3min.top',
bodered: false
},
{
id: 'aistudio',
name: 'AI Studio',
logo: AIStudioLogo,
url: 'https://aistudio.google.com/'
},
{
id: 'xiaoyi',
name: '小艺',
logo: XiaoYiAppLogo,
url: 'https://xiaoyi.huawei.com/chat/',
bodered: true
},
{
id: 'notebooklm',
name: 'NotebookLM',
logo: NotebookLMAppLogo,
url: 'https://notebooklm.google.com/',
},
{
id: 'coze',
name: 'Coze',
logo: CozeAppLogo,
url: 'https://www.coze.com/space',
bodered: true
}
]

View File

@ -66,6 +66,7 @@ import IbmModelLogo from '@renderer/assets/images/models/ibm.png'
import IbmModelLogoDark from '@renderer/assets/images/models/ibm_dark.png'
import InternlmModelLogo from '@renderer/assets/images/models/internlm.png'
import InternlmModelLogoDark from '@renderer/assets/images/models/internlm_dark.png'
import InternvlModelLogo from '@renderer/assets/images/models/internvl.png'
import JinaModelLogo from '@renderer/assets/images/models/jina.png'
import JinaModelLogoDark from '@renderer/assets/images/models/jina_dark.png'
import KeLingModelLogo from '@renderer/assets/images/models/keling.png'
@ -98,6 +99,8 @@ import NvidiaModelLogo from '@renderer/assets/images/models/nvidia.png'
import NvidiaModelLogoDark from '@renderer/assets/images/models/nvidia_dark.png'
import PalmModelLogo from '@renderer/assets/images/models/palm.png'
import PalmModelLogoDark from '@renderer/assets/images/models/palm_dark.png'
import PerplexityModelLogo from '@renderer/assets/images/models/perplexity.png'
import PerplexityModelLogoDark from '@renderer/assets/images/models/perplexity.png'
import PixtralModelLogo from '@renderer/assets/images/models/pixtral.png'
import PixtralModelLogoDark from '@renderer/assets/images/models/pixtral_dark.png'
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
@ -139,6 +142,7 @@ const visionAllowedModels = [
'glm-4v',
'qwen-vl',
'qwen2-vl',
'qwen2.5-vl',
'internvl2',
'grok-vision-beta',
'pixtral',
@ -159,7 +163,8 @@ export const VISION_REGEX = new RegExp(
export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus/i
export const REASONING_REGEX = /^(o\d+(?:-[\w-]+)?|.*\breasoner\b.*|.*-[rR]\d+.*)$/i
export const EMBEDDING_REGEX = /(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina)/i
export const EMBEDDING_REGEX =
/(?:^text-|embed|rerank|davinci|babbage|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina-clip|jina-embeddings)/i
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
export function getModelLogo(modelId: string) {
@ -182,11 +187,14 @@ export function getModelLogo(modelId: string) {
'babbage-': isLight ? ChatGptModelLogo : ChatGptModelLogoDakr,
'sora-': isLight ? ChatGptModelLogo : ChatGptModelLogoDakr,
'omni-': isLight ? ChatGptModelLogo : ChatGptModelLogoDakr,
'Embedding-V1': isLight ? WenxinModelLogo : WenxinModelLogoDark,
'text-embedding-v': isLight ? QwenModelLogo : QwenModelLogoDark,
'text-embedding': isLight ? ChatGptModelLogo : ChatGptModelLogoDakr,
'davinci-': isLight ? ChatGptModelLogo : ChatGptModelLogoDakr,
glm: isLight ? ChatGLMModelLogo : ChatGLMModelLogoDark,
deepseek: isLight ? DeepSeekModelLogo : DeepSeekModelLogoDark,
qwen: isLight ? QwenModelLogo : QwenModelLogoDark,
qwq: isLight ? QwenModelLogo : QwenModelLogoDark,
gemma: isLight ? GemmaModelLogo : GemmaModelLogoDark,
'yi-': isLight ? YiModelLogo : YiModelLogoDark,
llama: isLight ? LlamaModelLogo : LlamaModelLogoDark,
@ -221,6 +229,7 @@ export function getModelLogo(modelId: string) {
grok: isLight ? GrokModelLogo : GrokModelLogoDark,
hunyuan: isLight ? HunyuanModelLogo : HunyuanModelLogoDark,
internlm: isLight ? InternlmModelLogo : InternlmModelLogoDark,
internvl: InternvlModelLogo,
llava: isLight ? LLavaModelLogo : LLavaModelLogoDark,
magic: isLight ? MagicModelLogo : MagicModelLogoDark,
midjourney: isLight ? MidjourneyModelLogo : MidjourneyModelLogoDark,
@ -261,6 +270,8 @@ export function getModelLogo(modelId: string) {
'google/': isLight ? GoogleModelLogo : GoogleModelLogoDark,
hugging: isLight ? HuggingfaceModelLogo : HuggingfaceModelLogoDark,
embedding: isLight ? EmbeddingModelLogo : EmbeddingModelLogoDark,
perplexity: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
sonar: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
'bge-': BgeModelLogo
}
@ -313,6 +324,7 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
}
],
ollama: [],
lmstudio: [],
silicon: [
{
id: 'deepseek-ai/DeepSeek-R1',
@ -345,6 +357,68 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
group: 'BAAI'
}
],
ppio: [
{
id: 'deepseek/deepseek-r1/community',
name: 'DeepSeek: DeepSeek R1 (Community)',
provider: 'ppio',
group: 'deepseek'
},
{
id: 'deepseek/deepseek-v3/community',
name: 'DeepSeek: DeepSeek V3 (Community)',
provider: 'ppio',
group: 'deepseek'
},
{
id: 'deepseek/deepseek-r1',
provider: 'ppio',
name: 'DeepSeek R1',
group: 'deepseek'
},
{
id: 'deepseek/deepseek-v3',
provider: 'ppio',
name: 'DeepSeek V3',
group: 'deepseek'
},
{
id: 'qwen/qwen-2.5-72b-instruct',
provider: 'ppio',
name: 'Qwen2.5-72B-Instruct',
group: 'qwen'
},
{
id: 'qwen/qwen2.5-32b-instruct',
provider: 'ppio',
name: 'Qwen2.5-32B-Instruct',
group: 'qwen'
},
{
id: 'meta-llama/llama-3.1-70b-instruct',
provider: 'ppio',
name: 'Llama-3.1-70B-Instruct',
group: 'meta-llama'
},
{
id: 'meta-llama/llama-3.1-8b-instruct',
provider: 'ppio',
name: 'Llama-3.1-8B-Instruct',
group: 'meta-llama'
},
{
id: '01-ai/yi-1.5-34b-chat',
provider: 'ppio',
name: 'Yi-1.5-34B-Chat',
group: '01-ai'
},
{
id: '01-ai/yi-1.5-9b-chat',
provider: 'ppio',
name: 'Yi-1.5-9B-Chat',
group: '01-ai'
}
],
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' },
@ -405,6 +479,152 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
group: 'Claude 3'
}
],
'gitee-ai': [
{
id: 'DeepSeek-R1-Distill-Qwen-32B',
name: 'DeepSeek-R1-Distill-Qwen-32B',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'DeepSeek-R1-Distill-Qwen-1.5B',
name: 'DeepSeek-R1-Distill-Qwen-1.5B',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'DeepSeek-R1-Distill-Qwen-14B',
name: 'DeepSeek-R1-Distill-Qwen-14B',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'DeepSeek-R1-Distill-Qwen-7B',
name: 'DeepSeek-R1-Distill-Qwen-7B',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'DeepSeek-V3',
name: 'DeepSeek-V3',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'DeepSeek-R1',
name: 'DeepSeek-R1',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'deepseek-coder-33B-instruct',
name: 'deepseek-coder-33B-instruct',
provider: 'gitee-ai',
group: 'DeepSeek'
},
{
id: 'Qwen2.5-72B-Instruct',
name: 'Qwen2.5-72B-Instruct',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Qwen2.5-14B-Instruct',
name: 'Qwen2.5-14B-Instruct',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Qwen2-7B-Instruct',
name: 'Qwen2-7B-Instruct',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Qwen2.5-32B-Instruct',
name: 'Qwen2.5-32B-Instruct',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Qwen2-72B-Instruct',
name: 'Qwen2-72B-Instruct',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Qwen2-VL-72B',
name: 'Qwen2-VL-72B',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'QwQ-32B-Preview',
name: 'QwQ-32B-Preview',
provider: 'gitee-ai',
group: 'Qwen'
},
{
id: 'Yi-34B-Chat',
name: 'Yi-34B-Chat',
provider: 'gitee-ai',
group: '01-ai'
},
{
id: 'glm-4-9b-chat',
name: 'glm-4-9b-chat',
provider: 'gitee-ai',
group: 'THUDM'
},
{
id: 'codegeex4-all-9b',
name: 'codegeex4-all-9b',
provider: 'gitee-ai',
group: 'THUDM'
},
{
id: 'InternVL2-8B',
name: 'InternVL2-8B',
provider: 'gitee-ai',
group: 'OpenGVLab'
},
{
id: 'InternVL2.5-26B',
name: 'InternVL2.5-26B',
provider: 'gitee-ai',
group: 'OpenGVLab'
},
{
id: 'InternVL2.5-78B',
name: 'InternVL2.5-78B',
provider: 'gitee-ai',
group: 'OpenGVLab'
},
{
id: 'bge-large-zh-v1.5',
name: 'bge-large-zh-v1.5',
provider: 'gitee-ai',
group: 'BAAI'
},
{
id: 'bge-small-zh-v1.5',
name: 'bge-small-zh-v1.5',
provider: 'gitee-ai',
group: 'BAAI'
},
{
id: 'bge-m3',
name: 'bge-m3',
provider: 'gitee-ai',
group: 'BAAI'
},
{
id: 'bce-embedding-base_v1',
name: 'bce-embedding-base_v1',
provider: 'gitee-ai',
group: 'netease-youdao'
}
],
deepseek: [
{
id: 'deepseek-chat',
@ -446,6 +666,42 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
}
],
ocoolai: [
{
id: 'deepseek-chat',
provider: 'ocoolai',
name: 'deepseek-chat',
group: 'DeepSeek'
},
{
id: 'deepseek-reasoner',
provider: 'ocoolai',
name: 'deepseek-reasoner',
group: 'DeepSeek'
},
{
id: 'deepseek-ai/DeepSeek-R1',
provider: 'ocoolai',
name: 'deepseek-ai/DeepSeek-R1',
group: 'DeepSeek'
},
{
id: 'HiSpeed/DeepSeek-R1',
provider: 'ocoolai',
name: 'HiSpeed/DeepSeek-R1',
group: 'DeepSeek'
},
{
id: 'ocoolAI/DeepSeek-R1',
provider: 'ocoolai',
name: 'ocoolAI/DeepSeek-R1',
group: 'DeepSeek'
},
{
id: 'Azure/DeepSeek-R1',
provider: 'ocoolai',
name: 'Azure/DeepSeek-R1',
group: 'DeepSeek'
},
{
id: 'gpt-4o',
provider: 'ocoolai',
@ -458,12 +714,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
name: 'gpt-4o-all',
group: 'OpenAI'
},
{
id: 'gpt-4-all',
provider: 'ocoolai',
name: 'gpt-4-all',
group: 'OpenAI'
},
{
id: 'gpt-4o-mini',
provider: 'ocoolai',
@ -476,12 +726,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
name: 'gpt-4',
group: 'OpenAI'
},
{
id: 'gpt-4-turbo',
provider: 'ocoolai',
name: 'gpt-4-turbo',
group: 'OpenAI'
},
{
id: 'o1-preview',
provider: 'ocoolai',
@ -494,12 +738,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
name: 'o1-mini',
group: 'OpenAI'
},
{
id: 'gpt-3.5-turbo',
provider: 'ocoolai',
name: 'gpt-3.5-turbo',
group: 'OpenAI'
},
{
id: 'claude-3-5-sonnet-20240620',
provider: 'ocoolai',
@ -507,21 +745,9 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
group: 'Anthropic'
},
{
id: 'claude-3-opus-20240229',
id: 'claude-3-5-haiku-20241022',
provider: 'ocoolai',
name: 'claude-3-opus-20240229',
group: 'Anthropic'
},
{
id: 'claude-3-sonnet-20240229',
provider: 'ocoolai',
name: 'claude-3-sonnet-20240229',
group: 'Anthropic'
},
{
id: 'claude-3-haiku-20240307',
provider: 'ocoolai',
name: 'claude-3-haiku-20240307',
name: 'claude-3-5-haiku-20241022',
group: 'Anthropic'
},
{
@ -565,6 +791,30 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
provider: 'ocoolai',
name: 'gemma-2-9b-it',
group: 'Gemma'
},
{
id: 'Doubao-embedding',
provider: 'ocoolai',
name: 'Doubao-embedding',
group: 'Doubao'
},
{
id: 'text-embedding-3-large',
provider: 'ocoolai',
name: 'text-embedding-3-large',
group: 'Embedding'
},
{
id: 'text-embedding-3-small',
provider: 'ocoolai',
name: 'text-embedding-3-small',
group: 'Embedding'
},
{
id: 'text-embedding-v2',
provider: 'ocoolai',
name: 'text-embedding-v2',
group: 'Embedding'
}
],
github: [
@ -684,6 +934,38 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
group: 'Baichuan3'
}
],
modelscope: [
{
id: 'Qwen/Qwen2.5-72B-Instruct',
name: 'Qwen/Qwen2.5-72B-Instruct',
provider: 'modelscope',
group: 'Qwen'
},
{
id: 'Qwen/Qwen2.5-VL-72B-Instruct',
name: 'Qwen/Qwen2.5-VL-72B-Instruct',
provider: 'modelscope',
group: 'Qwen'
},
{
id: 'Qwen/Qwen2.5-Coder-32B-Instruct',
name: 'Qwen/Qwen2.5-Coder-32B-Instruct',
provider: 'modelscope',
group: 'Qwen'
},
{
id: 'deepseek-ai/DeepSeek-R1',
name: 'deepseek-ai/DeepSeek-R1',
provider: 'modelscope',
group: 'deepseek-ai'
},
{
id: 'deepseek-ai/DeepSeek-V3',
name: 'deepseek-ai/DeepSeek-V3',
provider: 'modelscope',
group: 'deepseek-ai'
}
],
bailian: [
{ 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' },
@ -1041,6 +1323,156 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
name: 'BGE Large EN',
group: 'Embedding'
}
],
dmxapi: [
{
id: 'gpt-3.5-turbo',
provider: 'dmxapi',
name: 'GPT-3.5-Turbo',
group: 'OpenAI'
},
{
id: 'gpt-4o',
provider: 'dmxapi',
name: 'GPT-4o',
group: 'OpenAI'
},
{
id: 'gpt-4o-mini',
provider: 'dmxapi',
name: 'GPT-4o-Mini',
group: 'OpenAI'
},
{
id: 'deepseek-reasoner',
provider: 'dmxapi',
name: 'DeepSeek Reasoner',
group: 'DeepSeek'
},
{
id: 'deepseek-chat',
provider: 'dmxapi',
name: 'DeepSeek Chat',
group: 'DeepSeek'
}
],
perplexity: [
{
id: 'sonar-reasoning-pro',
provider: 'perplexity',
name: 'sonar-reasoning-pro',
group: 'Sonar'
},
{
id: 'sonar-reasoning',
provider: 'perplexity',
name: 'sonar-reasoning',
group: 'Sonar'
},
{
id: 'sonar-pro',
provider: 'perplexity',
name: 'sonar-pro',
group: 'Sonar'
},
{
id: 'sonar',
provider: 'perplexity',
name: 'sonar',
group: 'Sonar'
}
],
infini: [
{
id: 'deepseek-r1',
provider: 'infini',
name: 'deepseek-r1',
group: 'DeepSeek'
},
{
id: 'deepseek-r1-distill-qwen-32b',
provider: 'infini',
name: 'deepseek-r1-distill-qwen-32b',
group: 'DeepSeek'
},
{
id: 'deepseek-v3',
provider: 'infini',
name: 'deepseek-v3',
group: 'DeepSeek'
},
{
id: 'qwen2.5-72b-instruct',
provider: 'infini',
name: 'qwen2.5-72b-instruct',
group: 'Qwen'
},
{
id: 'qwen2.5-32b-instruct',
provider: 'infini',
name: 'qwen2.5-32b-instruct',
group: 'Qwen'
},
{
id: 'qwen2.5-14b-instruct',
provider: 'infini',
name: 'qwen2.5-14b-instruct',
group: 'Qwen'
},
{
id: 'qwen2.5-7b-instruct',
provider: 'infini',
name: 'qwen2.5-7b-instruct',
group: 'Qwen'
},
{
id: 'qwen2-72b-instruct',
provider: 'infini',
name: 'qwen2-72b-instruct',
group: 'Qwen'
},
{
id: 'qwq-32b-preview',
provider: 'infini',
name: 'qwq-32b-preview',
group: 'Qwen'
},
{
id: 'qwen2.5-coder-32b-instruct',
provider: 'infini',
name: 'qwen2.5-coder-32b-instruct',
group: 'Qwen'
},
{
id: 'llama-3.3-70b-instruct',
provider: 'infini',
name: 'llama-3.3-70b-instruct',
group: 'Llama'
},
{
id: 'bge-m3',
provider: 'infini',
name: 'bge-m3',
group: 'BAAI'
},
{
id: 'gemma-2-27b-it',
provider: 'infini',
name: 'gemma-2-27b-it',
group: 'Gemma'
},
{
id: 'jina-embeddings-v2-base-zh',
provider: 'infini',
name: 'jina-embeddings-v2-base-zh',
group: 'Jina'
},
{
id: 'jina-embeddings-v2-base-code',
provider: 'infini',
name: 'jina-embeddings-v2-base-code',
group: 'Jina'
}
]
}
@ -1191,11 +1623,7 @@ export function isWebSearchModel(model: Model): boolean {
}
if (provider.id === 'aihubmix') {
const models = [
'gemini-2.0-flash-search',
'gemini-2.0-flash-exp-search',
'gemini-2.0-pro-exp-02-05-search'
]
const models = ['gemini-2.0-flash-search', 'gemini-2.0-flash-exp-search', 'gemini-2.0-pro-exp-02-05-search']
return models.includes(model?.id)
}

View File

@ -6,30 +6,36 @@ import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.p
import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png'
import BaiduCloudProviderLogo from '@renderer/assets/images/providers/baidu-cloud.svg'
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
import BytedanceProviderLogo from '@renderer/assets/images/providers/bytedance.png'
import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png'
import DmxapiProviderLogo from '@renderer/assets/images/providers/DMXAPI.png'
import FireworksProviderLogo from '@renderer/assets/images/providers/fireworks.png'
import GiteeAIProviderLogo from '@renderer/assets/images/providers/gitee-ai.png'
import GithubProviderLogo from '@renderer/assets/images/providers/github.png'
import GoogleProviderLogo from '@renderer/assets/images/providers/google.png'
import GraphRagProviderLogo from '@renderer/assets/images/providers/graph-rag.png'
import GrokProviderLogo from '@renderer/assets/images/providers/grok.png'
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
import HyperbolicProviderLogo from '@renderer/assets/images/providers/hyperbolic.png'
import InfiniProviderLogo from '@renderer/assets/images/providers/infini.png'
import JinaProviderLogo from '@renderer/assets/images/providers/jina.png'
import LMStudioProviderLogo from '@renderer/assets/images/providers/lmstudio.png'
import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png'
import MistralProviderLogo from '@renderer/assets/images/providers/mistral.png'
import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png'
import MoonshotProviderLogo from '@renderer/assets/images/providers/moonshot.png'
import NvidiaProviderLogo from '@renderer/assets/images/providers/nvidia.png'
import OcoolAiProviderLogo from '@renderer/assets/images/providers/ocoolai.png'
import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png'
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png'
import OpenRouterProviderLogo from '@renderer/assets/images/providers/openrouter.png'
import PerplexityProviderLogo from '@renderer/assets/images/providers/perplexity.png'
import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png'
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
import BytedanceProviderLogo from '@renderer/assets/images/providers/volcengine.png'
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
export function getProviderLogo(providerId: string) {
switch (providerId) {
case 'openai':
@ -38,6 +44,8 @@ export function getProviderLogo(providerId: string) {
return SiliconFlowProviderLogo
case 'deepseek':
return DeepSeekProviderLogo
case 'gitee-ai':
return GiteeAIProviderLogo
case 'yi':
return ZeroOneProviderLogo
case 'groq':
@ -46,6 +54,8 @@ export function getProviderLogo(providerId: string) {
return ZhipuProviderLogo
case 'ollama':
return OllamaProviderLogo
case 'lmstudio':
return LMStudioProviderLogo
case 'moonshot':
return MoonshotProviderLogo
case 'openrouter':
@ -54,6 +64,8 @@ export function getProviderLogo(providerId: string) {
return BaichuanProviderLogo
case 'dashscope':
return BailianProviderLogo
case 'modelscope':
return ModelScopeProviderLogo
case 'anthropic':
return AnthropicProviderLogo
case 'aihubmix':
@ -92,8 +104,16 @@ export function getProviderLogo(providerId: string) {
return MistralProviderLogo
case 'jina':
return JinaProviderLogo
case 'ppio':
return PPIOProviderLogo
case 'baidu-cloud':
return BaiduCloudProviderLogo
case 'dmxapi':
return DmxapiProviderLogo
case 'perplexity':
return PerplexityProviderLogo
case 'infini':
return InfiniProviderLogo
default:
return undefined
}
@ -111,6 +131,19 @@ export const PROVIDER_CONFIG = {
models: 'https://platform.openai.com/docs/models'
}
},
ppio: {
api: {
url: 'https://api.ppinfra.com/v3/openai'
},
websites: {
official:
'https://ppinfra.com/model-api/product/llm-api?utm_source=github_cherry-studio&utm_medium=github_readme&utm_campaign=link',
apiKey: 'https://ppinfra.com/settings/key-management',
docs: 'https://ppinfra.com/docs/model-api/reference/llm/llm.html',
models:
'https://ppinfra.com/model-api/product/llm-api?utm_source=github_cherry-studio&utm_medium=github_readme&utm_campaign=link'
}
},
gemini: {
api: {
url: 'https://generativelanguage.googleapis.com'
@ -133,6 +166,17 @@ export const PROVIDER_CONFIG = {
models: 'https://docs.siliconflow.cn/docs/model-names'
}
},
'gitee-ai': {
api: {
url: 'https://ai.gitee.com'
},
websites: {
official: 'https://ai.gitee.com/',
apiKey: 'https://ai.gitee.com/dashboard/settings/tokens',
docs: 'https://ai.gitee.com/docs/openapi/v1#tag/%E6%96%87%E6%9C%AC%E7%94%9F%E6%88%90/POST/chat/completions',
models: 'https://ai.gitee.com/serverless-api'
}
},
deepseek: {
api: {
url: 'https://api.deepseek.com'
@ -151,8 +195,8 @@ export const PROVIDER_CONFIG = {
websites: {
official: 'https://one.ocoolai.com/',
apiKey: 'https://one.ocoolai.com/token',
docs: 'https://docs.ooo.cool/',
models: 'https://docs.ooo.cool/guides/jiage/'
docs: 'https://docs.ocoolai.com/',
models: 'https://api.ocoolai.com/info/models/'
}
},
together: {
@ -166,6 +210,39 @@ export const PROVIDER_CONFIG = {
models: 'https://docs.together.ai/docs/chat-models'
}
},
dmxapi: {
api: {
url: 'https://api.dmxapi.com'
},
websites: {
official: 'https://dmxapi.com/',
apiKey: 'https://www.dmxapi.com/token',
docs: 'https://dmxapi.com/models.html#code-block',
models: 'https://www.dmxapi.com/pricing'
}
},
perplexity: {
api: {
url: 'https://api.perplexity.ai/'
},
websites: {
official: 'https://perplexity.ai/',
apiKey: 'https://www.perplexity.ai/settings/api',
docs: 'https://docs.perplexity.ai/home',
models: 'https://docs.perplexity.ai/guides/model-cards'
}
},
infini: {
api: {
url: 'https://cloud.infini-ai.com'
},
websites: {
official: 'https://cloud.infini-ai.com/',
apiKey: 'https://cloud.infini-ai.com/iam/secret/key',
docs: 'https://docs.infini-ai.com/gen-studio/api/maas.html#/operations/chatCompletions',
models: 'https://cloud.infini-ai.com/genstudio/model'
}
},
github: {
api: {
url: 'https://models.inference.ai.azure.com/'
@ -221,6 +298,17 @@ export const PROVIDER_CONFIG = {
models: 'https://platform.baichuan-ai.com/price'
}
},
modelscope: {
api: {
url: 'https://api-inference.modelscope.cn/v1/'
},
websites: {
official: 'https://modelscope.cn',
apiKey: 'https://modelscope.cn/my/myaccesstoken',
docs: 'https://modelscope.cn/docs/model-service/API-Inference/intro',
models: 'https://modelscope.cn/models'
}
},
dashscope: {
api: {
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
@ -294,7 +382,7 @@ export const PROVIDER_CONFIG = {
},
ollama: {
api: {
url: 'http://localhost:11434/v1/'
url: 'http://localhost:11434'
},
websites: {
official: 'https://ollama.com/',
@ -302,6 +390,16 @@ export const PROVIDER_CONFIG = {
models: 'https://ollama.com/library'
}
},
lmstudio: {
api: {
url: 'http://localhost:1234'
},
websites: {
official: 'https://lmstudio.ai/',
docs: 'https://lmstudio.ai/docs',
models: 'https://lmstudio.ai/models'
}
},
anthropic: {
api: {
url: 'https://api.anthropic.com/'

View File

@ -60,6 +60,7 @@ export const SyntaxHighlighterProvider: React.FC<PropsWithChildren> = ({ childre
const mappedLanguage = languageMap[language] || language
code = code.trimEnd()
const escapedCode = code?.replace(/[<>]/g, (char) => ({ '<': '&lt;', '>': '&gt;' })[char]!)
try {

View File

@ -138,15 +138,18 @@ export const useKnowledge = (baseId: string) => {
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)
if (item?.uniqueId && item?.uniqueIds) {
await window.api.knowledgeBase.remove({
uniqueId: item.uniqueId,
uniqueIds: item.uniqueIds,
base: getKnowledgeBaseParams(base)
})
}
}
if (item.type === 'file' && typeof item.content === 'object') {
await FileManager.deleteFile(item.content.id)
}
}
// 刷新项目
const refreshItem = async (item: KnowledgeItem) => {
const status = getProcessingStatus(item.id)
@ -155,8 +158,12 @@ export const useKnowledge = (baseId: string) => {
return
}
if (base && item.uniqueId) {
await window.api.knowledgeBase.remove({ uniqueId: item.uniqueId, base: getKnowledgeBaseParams(base) })
if (base && item.uniqueId && item.uniqueIds) {
await window.api.knowledgeBase.remove({
uniqueId: item.uniqueId,
uniqueIds: item.uniqueIds,
base: getKnowledgeBaseParams(base)
})
updateItem({
...item,
processingStatus: 'pending',

View File

@ -0,0 +1,18 @@
import store, { useAppSelector } from '@renderer/store'
import { setLMStudioKeepAliveTime } from '@renderer/store/llm'
import { useDispatch } from 'react-redux'
export function useLMStudioSettings() {
const settings = useAppSelector((state) => state.llm.settings.lmstudio)
const dispatch = useDispatch()
return { ...settings, setKeepAliveTime: (time: number) => dispatch(setLMStudioKeepAliveTime(time)) }
}
export function getLMStudioSettings() {
return store.getState().llm.settings.lmstudio
}
export function getLMStudioKeepAliveTime() {
return store.getState().llm.settings.lmstudio.keepAliveTime + 'm'
}

View File

@ -0,0 +1,8 @@
import { SidebarIcon } from '@renderer/types'
import { useSettings } from './useSettings'
export function useSidebarIconShow(icon: SidebarIcon) {
const { sidebarIcons } = useSettings()
return sidebarIcons.visible.includes(icon)
}

View File

@ -25,6 +25,10 @@ export function useActiveTopic(_assistant: Assistant, topic?: Topic) {
return { activeTopic, setActiveTopic }
}
export function useTopic(assistant: Assistant, topicId?: string) {
return assistant?.topics.find((topic) => topic.id === topicId)
}
export function getTopic(assistant: Assistant, topicId: string) {
return assistant?.topics.find((topic) => topic.id === topicId)
}

View File

@ -2,12 +2,12 @@
"translation": {
"agents": {
"add.button": "Add to Assistant",
"add.knowledge_base": "Knowledge Base",
"add.knowledge_base.placeholder": "Select Knowledge Base",
"add.name": "Name",
"add.name.placeholder": "Enter name",
"add.prompt": "Prompt",
"add.prompt.placeholder": "Enter prompt",
"add.knowledge_base": "Knowledge Base",
"add.knowledge_base.placeholder": "Select Knowledge Base",
"add.title": "Create Agent",
"delete.popup.content": "Are you sure you want to delete this agent?",
"edit.message.add.title": "Add",
@ -42,20 +42,25 @@
"save.success": "Saved successfully",
"save.title": "Save to agent",
"search": "Search assistants...",
"settings.reasoning_effort": "Reasoning effort",
"settings.reasoning_effort.tip": "Only supports reasoning models",
"settings.reasoning_effort.low": "low",
"settings.reasoning_effort.medium": "medium",
"settings.reasoning_effort.high": "high",
"settings.auto_reset_model": "Auto Reset Model",
"settings.auto_reset_model.tip": "Automatically reset the model when a new topic is created.",
"settings.default_model": "Default Model",
"settings.knowledge_base": "Knowledge Base Settings",
"settings.model": "Model Settings",
"settings.preset_messages": "Preset Messages",
"settings.prompt": "Prompt Settings",
"settings.knowledge_base": "Knowledge Base Settings",
"settings.reasoning_effort": "Reasoning effort",
"settings.reasoning_effort.high": "high",
"settings.reasoning_effort.low": "low",
"settings.reasoning_effort.medium": "medium",
"settings.reasoning_effort.tip": "Only supports reasoning models",
"title": "Assistants"
},
"auth": {
"error": "API key automatically obtained failed, please get it manually",
"get_key": "Get",
"get_key_success": "API key automatically obtained successfully",
"login": "Login",
"oauth_button": "Auth with {{provider}}"
},
"button": {
"add": "Add",
"added": "Added",
@ -69,6 +74,7 @@
"artifacts.button.download": "Download",
"artifacts.button.preview": "Preview",
"assistant.search.placeholder": "Search",
"deeply_thought": "Deeply thought ({{secounds}} seconds)",
"default.description": "Hello, I'm Default Assistant. You can start chatting with me right away",
"default.name": "⭐️ Default Assistant",
"default.topic.name": "Default Topic",
@ -79,6 +85,7 @@
"input.context_count.tip": "Context Count",
"input.estimated_tokens.tip": "Estimated tokens",
"input.expand": "Expand",
"input.knowledge_base": "Knowledge Base",
"input.new.context": "Clear Context {{Command}}",
"input.new_topic": "New Topic {{Command}}",
"input.pause": "Pause",
@ -90,19 +97,20 @@
"input.upload": "Upload image or document file",
"input.upload.document": "Upload document file (model does not support images)",
"input.web_search": "Enable web search",
"input.knowledge_base": "Knowledge Base",
"input.file_not_supported": "Model does not support this file type",
"message.new.branch": "New Branch",
"message.new.branch.created": "New Branch Created",
"message.regenerate.model": "Switch Model",
"message.new.context": "New Context",
"message.regenerate.model": "Switch Model",
"message.useful": "Helpful",
"resend": "Resend",
"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.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.max_tokens.tip": "The maximum number of tokens the model can generate. Need to consider the context limit of the model, otherwise an error will be reported",
"settings.reset": "Reset",
"settings.set_as_default": "Apply to default assistant",
"settings.show_line_numbers": "Show line numbers in code",
@ -110,38 +118,42 @@
"settings.temperature.tip": "Higher values make the model more creative and unpredictable, while lower 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",
"settings.max_tokens.confirm": "Enable max tokens limit",
"settings.max_tokens.confirm_content": "Enable max tokens limit, affects the length of the result. Need to consider the context limit of the model, otherwise an error will be reported",
"suggestions.title": "Suggested Questions",
"thinking": "Thinking",
"topics.auto_rename": "Auto Rename",
"topics.clear.title": "Clear Messages",
"topics.edit.placeholder": "Enter new name",
"topics.edit.title": "Edit Name",
"topics.export.image": "Export as image",
"topics.export.notion": "Export to Notion",
"topics.export.md": "Export as markdown",
"topics.export.notion": "Export to Notion",
"topics.export.title": "Export",
"topics.pinned": "Pinned Topics",
"topics.unpinned": "Unpinned Topics",
"topics.export.word": "Export as Word",
"topics.list": "Topic List",
"topics.move_to": "Move to",
"topics.pinned": "Pinned Topics",
"topics.title": "Topics",
"topics.unpinned": "Unpinned Topics",
"translate": "Translate",
"resend": "Resend",
"thinking": "Thinking",
"deeply_thought": "Deeply thought ({{secounds}} seconds)"
"topics.prompt": "Topic Prompts",
"topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic",
"topics.prompt.edit.title": "Edit Topic Prompts"
},
"common": {
"add": "Add",
"and": "and",
"assistant": "Assistant",
"avatar": "Avatar",
"back": "Back",
"cancel": "Cancel",
"chat": "Chat",
"clear": "Clear",
"close": "Close",
"copy": "Copy",
"cut": "Cut",
"default": "Default",
"knowledge_base": "Knowledge Base",
"delete": "Delete",
"description": "Description",
"docs": "Docs",
@ -149,6 +161,7 @@
"duplicate": "Duplicate",
"edit": "Edit",
"footnotes": "References",
"knowledge_base": "Knowledge Base",
"language": "Language",
"model": "Model",
"models": "Models",
@ -165,18 +178,11 @@
"topics": "Topics",
"warning": "Warning",
"you": "You",
"clear": "Clear",
"add": "Add"
"footnote": "Reference content"
},
"error": {
"backup.file_format": "Backup file format error",
"chat.response": "Something went wrong. Please check if you have set your API key in the Settings > Providers",
"no_api_key": "API key is not configured",
"provider_disabled": "Model provider is not enabled",
"render": {
"title": "Render Error",
"description": "Failed to render formula. Please check if the formula format is correct"
},
"http": {
"400": "Request failed. Please check if the request parameters are correct. If you have changed the model settings, please reset them to the default settings",
"401": "Authentication failed. Please check if your API key is correct",
@ -187,6 +193,13 @@
"502": "Gateway error. Please try again later",
"503": "Service unavailable. Please try again later",
"504": "Gateway timeout. Please try again later"
},
"model.exists": "Model already exists",
"no_api_key": "API key is not configured",
"provider_disabled": "Model provider is not enabled",
"render": {
"description": "Failed to render formula. Please check if the formula format is correct",
"title": "Render Error"
}
},
"export": {
@ -204,20 +217,20 @@
"all": "All Files",
"count": "Count",
"created_at": "Created At",
"delete": "Delete",
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?",
"delete.paintings.warning": "Image contains this file, deletion is not possible",
"delete.title": "Delete File",
"document": "Document",
"edit": "Edit",
"file": "File",
"image": "Image",
"name": "Name",
"open": "Open",
"size": "Size",
"type": "Type",
"text": "Text",
"title": "Files",
"edit": "Edit",
"delete": "Delete",
"delete.title": "Delete File",
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?",
"delete.paintings.warning": "Image contains this file, deletion is not possible"
"type": "Type"
},
"history": {
"continue_chat": "Continue Chatting",
@ -227,6 +240,69 @@
"search.topics.empty": "No topics found, press Enter to search all messages",
"title": "Topics Search"
},
"knowledge": {
"add": {
"title": "Add Knowledge Base"
},
"add_directory": "Add Directory",
"add_file": "Add File",
"add_note": "Add Note",
"add_sitemap": "Website Map",
"add_url": "Add URL",
"cancel_index": "Cancel Indexing",
"chunk_overlap": "Chunk Overlap",
"chunk_overlap_placeholder": "Default (not recommended to change)",
"chunk_overlap_tooltip": "The amount of duplicate content between adjacent chunks, ensuring that the chunks are still contextually related, improving the overall effect of processing long text",
"chunk_size": "Chunk Size",
"chunk_size_change_warning": "Chunk size and overlap size changes only apply to new content",
"chunk_size_placeholder": "Default (not recommended to change)",
"chunk_size_too_large": "Chunk size cannot exceed model context limit ({{max_context}})",
"chunk_size_tooltip": "Split documents into chunks, each chunk size, not exceeding model context limit",
"clear_selection": "Clear selection",
"delete": "Delete",
"delete_confirm": "Are you sure you want to delete this knowledge base?",
"directories": "Directories",
"directory_placeholder": "Enter Directory Path",
"document_count": "Requested Document Chunks",
"document_count_default": "Default",
"document_count_help": "The more document chunks requested, the more information is included, but the more tokens are consumed",
"drag_file": "Drag file here",
"empty": "No knowledge base found",
"file_hint": "Support {{file_types}}",
"index_all": "Index All",
"index_cancelled": "Indexing cancelled",
"index_started": "Indexing started",
"invalid_url": "Invalid URL",
"model_info": "Model Info",
"no_bases": "No knowledge bases available",
"no_provider": "Knowledge base model provider is not set, the knowledge base will no longer be supported, please create a new knowledge base",
"not_set": "Not Set",
"not_support": "Knowledge base database engine updated, the knowledge base will no longer be supported, please create a new knowledge base",
"notes": "Notes",
"notes_placeholder": "Enter additional information or context for this knowledge base...",
"rename": "Rename",
"search": "Search knowledge base",
"search_placeholder": "Enter text to search",
"settings": "Knowledge Base Settings",
"sitemap_placeholder": "Enter Website Map URL",
"sitemaps": "Websites",
"source": "Source",
"status": "Status",
"status_completed": "Completed",
"status_failed": "Failed",
"status_new": "Added",
"status_pending": "Pending",
"status_processing": "Processing",
"title": "Knowledge Base",
"url_added": "URL added",
"url_placeholder": "Enter URL, multiple URLs separated by Enter",
"urls": "URLs",
"threshold_tooltip": "Used to evaluate the relevance between the user's question and the content in the knowledge base (0-1)",
"threshold_placeholder": "Not set",
"threshold_too_large_or_small": "Threshold cannot be greater than 1 or less than 0",
"no_match": "No matching content found in the knowledge base.",
"threshold": "Matching threshold"
},
"languages": {
"arabic": "Arabic",
"chinese": "Chinese",
@ -256,61 +332,114 @@
"title": "Mermaid Diagram"
},
"message": {
"api.check.model.title": "Select the model to use for detection",
"api.connection.failed": "Connection failed",
"api.connection.success": "Connection successful",
"api.check.model.title": "Select the model to use for detection",
"assistant.added.content": "Assistant added successfully",
"backup.failed": "Backup failed",
"backup.success": "Backup successful",
"backup.start.success": "Backup started",
"backup.success": "Backup successful",
"chat.completion.paused": "Chat completion paused",
"citations": "References",
"copied": "Copied!",
"copy.success": "Copied!",
"error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size",
"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.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size",
"error.get_embedding_dimensions": "Failed to get embedding dimensions",
"error.invalid.enter.model": "Please select a model",
"error.invalid.proxy.url": "Invalid proxy URL",
"error.invalid.webdav": "Invalid WebDAV settings",
"error.invalid.enter.api.host": "Please enter a valid API host",
"error.invalid.enter.api.key": "Please enter a valid API key",
"error.invalid.enter.model": "Please select a model",
"error.notion.export": "Notion import failed",
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
"group.delete.title": "Delete Group Message",
"mention.title": "Switch model answer",
"message.code_style": "Code style",
"message.delete.content": "Are you sure you want to delete this message?",
"message.delete.title": "Delete Message",
"message.multi_model_style": "Group style",
"message.multi_model_style.fold": "Fold",
"message.multi_model_style.horizontal": "Horizontal",
"message.multi_model_style.vertical": "Vertical",
"message.style": "Message style",
"message.style.bubble": "Bubble",
"message.style.plain": "Plain",
"message.multi_model_style": "Multi-model answer style",
"message.multi_model_style.horizontal": "Horizontal",
"message.multi_model_style.vertical": "Vertical",
"message.multi_model_style.fold": "Fold",
"regenerate.confirm": "Regenerating will replace current message",
"reset.confirm.content": "Are you sure you want to clear all data?",
"reset.double.confirm.content": "All data will be lost, do you want to continue?",
"reset.double.confirm.title": "DATA LOST !!!",
"restore.success": "Restored successfully",
"save.success.title": "Saved successfully",
"success.notion.export": "Notion import successful",
"switch.disabled": "Please wait for the current reply to complete",
"topic.added": "New topic added",
"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",
"copy.success": "Copied!",
"error.get_embedding_dimensions": "Failed to get embedding dimensions",
"group.delete.title": "Delete Group Message",
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
"mention.title": "Switch model answer",
"error.notion.export": "Notion import failed",
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
"success.notion.export": "Notion import successful",
"warn.notion.exporting": "Notion is importing, please do not import repeatedly",
"citations": "References"
"error.invalid.api.host": "Invalid API Host",
"error.invalid.api.key": "Invalid API Key"
},
"minapp": {
"title": "MinApp",
"sidebar.add.title": "Add to sidebar",
"sidebar.remove.title": "Remove from sidebar"
"sidebar.remove.title": "Remove from sidebar",
"title": "MinApp"
},
"miniwindow": {
"clipboard": {
"empty": "Clipboard is empty"
},
"feature": {
"chat": "Answer this question",
"explanation": "Explanation",
"summary": "Content summary",
"translate": "Text translation"
},
"footer": {
"copy_last_message": "Press C to copy",
"esc": "Press ESC {{action}}",
"esc_back": "back",
"esc_close": "close the window"
},
"input": {
"placeholder": {
"empty": "Ask {{model}} for help...",
"title": "What do you want to do with this text?"
}
}
},
"models": {
"add_parameter": "Add Parameter",
"all": "All",
"custom_parameters": "Custom Parameters",
"dimensions": "Dimensions {{dimensions}}",
"embedding": "Embedding",
"embedding_model": "Embedding Model",
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage",
"free": "Free",
"parameter_name": "Parameter Name",
"parameter_type": {
"boolean": "Boolean",
"json": "JSON",
"number": "Number",
"string": "Text"
},
"pinned": "Pinned",
"reasoning": "Reasoning",
"search": "Search models...",
"stream_output": "Stream output",
"type": {
"embedding": "Embedding",
"reasoning": "Reasoning",
"select": "Select Model Types",
"text": "Text",
"vision": "Vision"
},
"vision": "Vision",
"websearch": "WebSearch"
},
"ollama": {
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.",
@ -318,6 +447,12 @@
"keep_alive_time.title": "Keep Alive Time",
"title": "Ollama"
},
"lmstudio": {
"keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.",
"keep_alive_time.placeholder": "Minutes",
"keep_alive_time.title": "Keep Alive Time",
"title": "LM Studio"
},
"paintings": {
"button.delete.image": "Delete Image",
"button.delete.image.confirm": "Are you sure you want to delete this image?",
@ -331,25 +466,35 @@
"negative_prompt_tip": "Describe what you don't want included in the image",
"number_images": "Number Images",
"number_images_tip": "Number of images to generate (1-4)",
"prompt_enhancement": "Prompt Enhancement",
"prompt_enhancement_tip": "Rewrite prompts into detailed, model-friendly versions when switched on",
"prompt_placeholder": "Describe the image you want to create, e.g. A serene lake at sunset with mountains in the background",
"regenerate.confirm": "This will replace your existing generated images. Do you want to continue?",
"seed": "Seed",
"seed_tip": "The same seed and prompt can produce similar images",
"title": "Images",
"prompt_enhancement": "Prompt Enhancement",
"prompt_enhancement_tip": "Rewrite prompts into detailed, model-friendly versions when switched on"
"title": "Images"
},
"prompts": {
"explanation": "Explain this concept to me",
"summarize": "Summarize this text",
"title": "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."
},
"provider": {
"infini": "Infini",
"perplexity": "Perplexity",
"dmxapi": "DMXAPI",
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baidu-cloud": "Baidu Cloud",
"baichuan": "Baichuan",
"baidu-cloud": "Baidu Cloud",
"dashscope": "Alibaba Cloud",
"modelscope": "ModelScope",
"deepseek": "DeepSeek",
"doubao": "Doubao",
"doubao": "Volcengine",
"fireworks": "Fireworks",
"gemini": "Gemini",
"gitee-ai": "Gitee AI",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
@ -363,21 +508,23 @@
"nvidia": "Nvidia",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"lmstudio": "LM Studio",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"ppio": "PPIO",
"qwenlm": "QwenLM",
"silicon": "SiliconFlow",
"stepfun": "StepFun",
"together": "Together",
"yi": "Yi",
"zhinao": "360AI",
"zhipu": "ZHIPU AI",
"qwenlm": "QwenLM"
"zhipu": "ZHIPU AI"
},
"settings": {
"about": "About & Feedback",
"about.checkingUpdate": "Checking for updates...",
"about.checkUpdate": "Check Update",
"about.checkUpdate.available": "Update",
"about.checkingUpdate": "Checking for updates...",
"about.contact.button": "Email",
"about.contact.title": "Contact",
"about.description": "A powerful AI assistant for producer",
@ -388,13 +535,13 @@
"about.license.title": "License",
"about.releases.button": "Releases",
"about.releases.title": "Release Notes",
"about.social.title": "Social Accounts",
"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",
@ -411,42 +558,61 @@
"title": "Clear Cache"
},
"data.title": "Data Directory",
"notion.api_key": "Notion API Key",
"notion.database_id": "Notion Database ID",
"notion.title": "Notion Configuration",
"notion.check": {
"button": "Check",
"fail": "Connection failed, please check the configuration",
"success": "Connection successful",
"error": "Connection error, please check the network",
"empty_api_key": "Api_key is not configured",
"empty_database_id": "Database_id is not configured"
},
"title": "Data Settings",
"webdav.autoSync": "Auto Backup",
"webdav.autoSync.off": "Off",
"webdav.backup.button": "Backup to WebDAV",
"webdav.host": "WebDAV Host",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.hours": "Hours",
"webdav.lastSync": "Last Backup",
"webdav.minutes": "Minutes",
"webdav.noSync": "Waiting for next backup",
"webdav.password": "WebDAV Password",
"webdav.path": "WebDAV Path",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "Auto Backup",
"webdav.minute": "Minute",
"webdav.minutes": "Minutes",
"webdav.hour": "Hour",
"webdav.hours": "Hours",
"webdav.restore.button": "Restore from WebDAV",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV User",
"webdav.syncStatus": "Backup Status",
"webdav.autoSync.off": "Off",
"webdav.noSync": "Waiting for next backup",
"webdav.restore.content": "Restore from WebDAV will overwrite the current data, continue?",
"webdav.restore.title": "Restore from WebDAV",
"webdav.syncError": "Backup Error",
"webdav.lastSync": "Last Backup",
"notion.api_key":"Notion API Key",
"notion.database_id":"Notion Database ID",
"notion.title":"Notion Configuration"
},
"quickAssistant": {
"title": "Quick Assistant",
"click_tray_to_show": "Click the tray icon to start",
"enable_quick_assistant": "Enable Quick Assistant",
"use_shortcut_to_show": "Right-click the tray icon or use shortcuts to start"
"webdav.syncStatus": "Backup Status",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV User"
},
"display.custom.css": "Custom CSS",
"display.custom.css.placeholder": "/* Put custom CSS here */",
"display.minApp.disabled": "Hidden MinApp",
"display.minApp.empty": "Drag minApp from the left to hide them here",
"display.minApp.title": "MinApp Settings",
"display.minApp.visible": "Visible MinApp",
"display.sidebar.chat.hiddenMessage": "Assistants are basic functions, not supported for hiding",
"display.sidebar.disabled": "Hide icons",
"display.sidebar.empty": "Drag the hidden feature from the left side here",
"display.sidebar.files.icon": "Show Files icon",
"display.sidebar.knowledge.icon": "Show Knowledge icon",
"display.sidebar.minapp.icon": "Show MinApp icon",
"display.sidebar.painting.icon": "Show Painting icon",
"display.sidebar.title": "Sidebar Settings",
"display.sidebar.translate.icon": "Show Translate icon",
"display.sidebar.visible": "Show icons",
"display.title": "Display Settings",
"display.topic.title": "Topic Settings",
"font_size.title": "Message font size",
"general": "General Settings",
"general.backup.button": "Backup",
"general.backup.title": "Data Backup and Recovery",
"general.display.title": "Display Settings",
"general.manually_check_update.title": "Turn off update checking",
"general.reset.button": "Reset",
"general.reset.title": "Data Reset",
@ -455,25 +621,6 @@
"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.translate.icon": "Show Translate icon",
"display.sidebar.painting.icon": "Show Painting icon",
"display.sidebar.minapp.icon": "Show MinApp icon",
"display.sidebar.knowledge.icon": "Show Knowledge icon",
"display.sidebar.files.icon": "Show Files icon",
"display.sidebar.title": "Sidebar Settings",
"display.sidebar.visible": "Show icons",
"display.sidebar.disabled": "Hide icons",
"display.sidebar.chat.hiddenMessage": "Assistants are basic functions, not supported for hiding",
"display.sidebar.empty": "Drag the hidden feature from the left side here",
"display.minApp.title": "MinApp Settings",
"display.minApp.visible": "Visible MinApp",
"display.minApp.disabled": "Hidden MinApp",
"display.minApp.empty": "Drag minApp from the left to hide them here",
"": "MinApp that have been added to the sidebar do not support hiding. If you want to hide them, please remove them from the sidebar first.",
"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",
"input.target_language": "Target language",
"input.target_language.chinese": "Simplified Chinese",
@ -483,16 +630,16 @@
"input.target_language.russian": "Russian",
"messages.divider": "Show divider between messages",
"messages.input.paste_long_text_as_file": "Paste long text as file",
"messages.input.paste_long_text_threshold": "Paste long text length",
"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 message",
"messages.math_engine": "Math render engine",
"messages.math_engine": "Math engine",
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
"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",
@ -506,15 +653,15 @@
"models.default_assistant_model": "Default Assistant Model",
"models.default_assistant_model_description": "Model used when creating a new assistant, if the assistant is not set, this model will be used",
"models.empty": "No models found",
"models.enable_topic_naming": "Topic Auto Naming",
"models.topic_naming_model": "Topic Naming Model",
"models.topic_naming_model_description": "Model used when automatically naming a new topic",
"models.topic_naming_model_setting_title": "Topic Naming Model Settings",
"models.topic_naming_prompt": "Topic Naming Prompt",
"models.translate_model": "Translate Model",
"models.translate_model_description": "Model used for translation service",
"models.translate_model_prompt_message": "Please enter the translate model prompt",
"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",
@ -527,6 +674,7 @@
"api_key": "API Key",
"api_key.tip": "Multiple keys separated by commas",
"api_version": "API Version",
"charge": "Charge",
"check": "Check",
"check_all_keys": "Check All Keys",
"check_multiple_keys": "Check Multiple API Keys",
@ -535,7 +683,6 @@
"docs_check": "Check",
"docs_more_details": "for more details",
"get_api_key": "Get API Key",
"charge": "Charge",
"no_models": "Please add models first before checking the API connection",
"not_checked": "Not Checked",
"remove_duplicate_keys": "Remove Duplicate Keys",
@ -543,17 +690,6 @@
"search_placeholder": "Search model id or name",
"title": "Model Provider"
},
"provider.api.url.preview": "Preview: {{url}}",
"provider.api.url.reset": "Reset",
"provider.api.url.tip": "Ending with / ignores v1, ending with # forces use of input address",
"provider.api_host": "API Host",
"provider.api_key": "API Key",
"provider.api_key.tip": "Multiple keys separated by commas",
"provider.api_version": "API Version",
"provider.check": "Check",
"provider.docs_check": "Check",
"provider.docs_more_details": "for more details",
"provider.search_placeholder": "Search model id or name",
"proxy": {
"mode": {
"custom": "Custom Proxy",
@ -564,28 +700,34 @@
"title": "Proxy Settings"
},
"proxy.title": "Proxy Address",
"quickAssistant": {
"click_tray_to_show": "Click the tray icon to start",
"enable_quick_assistant": "Enable Quick Assistant",
"title": "Quick Assistant",
"use_shortcut_to_show": "Right-click the tray icon or use shortcuts to start"
},
"shortcuts": {
"action": "Action",
"alt_warning": "Mac does not support Option + letters as shortcuts",
"clear_shortcut": "Clear Shortcut",
"clear_topic": "Clear Messages",
"copy_last_message": "Copy Last Message",
"key": "Key",
"mini_window": "Quick Assistant",
"new_topic": "New Topic",
"title": "Keyboard Shortcuts",
"zoom_in": "Zoom In",
"zoom_out": "Zoom Out",
"zoom_reset": "Reset Zoom",
"show_app": "Show App",
"press_shortcut": "Press Shortcut",
"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 Option + letters as shortcuts",
"reset_to_default": "Reset to Default",
"clear_shortcut": "Clear Shortcut",
"search_message": "Search Message",
"show_app": "Show App",
"title": "Keyboard Shortcuts",
"toggle_new_context": "Clear Context",
"toggle_show_assistants": "Toggle Assistants",
"toggle_show_topics": "Toggle Topics",
"copy_last_message": "Copy Last Message",
"search_message": "Search Message",
"mini_window": "Quick Assistant",
"clear_topic": "Clear Messages",
"toggle_new_context": "Clear Context"
"zoom_in": "Zoom In",
"zoom_out": "Zoom Out",
"zoom_reset": "Reset Zoom"
},
"theme.auto": "Auto",
"theme.dark": "Dark",
@ -604,151 +746,31 @@
"translate": {
"any.language": "Any language",
"button.translate": "Translate",
"close": "Close",
"confirm": {
"content": "Translation will replace the original text, continue?",
"title": "Translation Confirmation"
},
"error.not_configured": "Translation model is not configured",
"error.failed": "Translation failed",
"error.not_configured": "Translation model is not configured",
"input.placeholder": "Enter text to translate",
"output.placeholder": "Translation",
"processing": "Translation in progress...",
"title": "Translation",
"close": "Close"
"title": "Translation"
},
"tray": {
"quit": "Quit",
"show_window": "Show Window",
"show_mini_window": "Quick Assistant"
"show_mini_window": "Quick Assistant",
"show_window": "Show Window"
},
"words": {
"knowledgeGraph": "Knowledge Graph",
"visualization": "Visualization",
"quit": "Quit",
"show_window": "Show Window",
"quit": "Quit"
"visualization": "Visualization"
},
"knowledge": {
"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",
"no_provider": "Knowledge base model provider is not set, the knowledge base will no longer be supported, please create a new knowledge base",
"source": "Source",
"chunk_size": "Chunk Size",
"chunk_overlap": "Chunk Overlap",
"not_set": "Not Set",
"settings": "Knowledge Base Settings",
"document_count": "Requested Document Count",
"document_count_help": "The more documents requested, the more information is included, but the more tokens are consumed",
"document_count_default": "Default",
"chunk_size_placeholder": "Default (not recommended to change)",
"chunk_overlap_placeholder": "Default (not recommended to change)",
"chunk_size_tooltip": "Split documents into chunks, each chunk size, not exceeding model context limit",
"chunk_overlap_tooltip": "The amount of duplicate content between adjacent chunks, ensuring that the chunks are still contextually related, improving the overall effect of processing long text",
"chunk_size_change_warning": "Chunk size and overlap size changes only apply to new content",
"chunk_size_too_large": "Chunk size cannot exceed model context limit ({{max_context}})"
},
"models": {
"pinned": "Pinned",
"search": "Search models...",
"stream_output": "Stream output",
"type": {
"select": "Select Model Types",
"text": "Text",
"vision": "Vision",
"embedding": "Embedding",
"reasoning": "Reasoning"
},
"all": "All",
"vision": "Vision",
"websearch": "WebSearch",
"free": "Free",
"reasoning": "Reasoning",
"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",
"json": "JSON"
}
},
"prompts": {
"title": "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.",
"explanation": "Explain this concept to me",
"summarize": "Summarize this text"
},
"miniwindow": {
"feature": {
"chat": "Answer this question",
"translate": "Text translation",
"summary": "Content summary",
"explanation": "Explanation"
},
"clipboard": {
"empty": "Clipboard is empty"
},
"input": {
"placeholder": {
"title": "What do you want to do with this text?",
"empty": "Ask {{model}} for help..."
}
},
"footer": {
"esc": "Press ESC {{action}}",
"esc_close": "close the window",
"esc_back": "back",
"copy_last_message": "Press C to copy"
}
},
"auth": {
"oauth_button": "Auth with {{provider}}",
"get_key": "Get",
"get_key_success": "API key automatically obtained successfully",
"login": "Login",
"error": "API key automatically obtained failed, please get it manually"
"docs": {
"title": "Docs"
}
}
}

View File

@ -2,12 +2,12 @@
"translation": {
"agents": {
"add.button": "アシスタントに追加",
"add.knowledge_base": "ナレッジベース",
"add.knowledge_base.placeholder": "ナレッジベースを選択",
"add.name": "名前",
"add.name.placeholder": "名前を入力",
"add.prompt": "プロンプト",
"add.prompt.placeholder": "プロンプトを入力",
"add.knowledge_base": "ナレッジベース",
"add.knowledge_base.placeholder": "ナレッジベースを選択",
"add.title": "エージェントを作成",
"delete.popup.content": "このエージェントを削除してもよろしいですか?",
"edit.message.add.title": "追加",
@ -42,14 +42,24 @@
"save.success": "保存に成功しました",
"save.title": "エージェントに保存",
"search": "アシスタントを検索...",
"settings.auto_reset_model": "自動リセットモデル",
"settings.auto_reset_model.tip": "新しいトピックを作成する際にモデルを自動的にリセットします",
"settings.default_model": "デフォルトモデル",
"settings.knowledge_base": "ナレッジベース設定",
"settings.model": "モデル設定",
"settings.preset_messages": "プリセットメッセージ",
"settings.prompt": "プロンプト設定",
"settings.knowledge_base": "ナレッジベース設定",
"title": "アシスタント"
"title": "アシスタント",
"settings.reasoning_effort": "思考連鎖の長さ",
"settings.reasoning_effort.high": "長い",
"settings.reasoning_effort.low": "短い",
"settings.reasoning_effort.medium": "中程度",
"settings.reasoning_effort.tip": "この設定は推論モデルのみサポートしています"
},
"auth": {
"error": "APIキーの自動取得に失敗しました。手動で取得してください",
"get_key": "取得",
"get_key_success": "APIキーの自動取得に成功しました",
"login": "認証",
"oauth_button": "{{provider}}で認証"
},
"button": {
"add": "追加",
@ -64,6 +74,7 @@
"artifacts.button.download": "ダウンロード",
"artifacts.button.preview": "プレビュー",
"assistant.search.placeholder": "検索",
"deeply_thought": "深く考えています({{secounds}} 秒)",
"default.description": "こんにちは、私はデフォルトのアシスタントです。すぐにチャットを始められます。",
"default.name": "⭐️ デフォルトアシスタント",
"default.topic.name": "デフォルトトピック",
@ -74,6 +85,7 @@
"input.context_count.tip": "コンテキスト数",
"input.estimated_tokens.tip": "推定トークン数",
"input.expand": "展開",
"input.knowledge_base": "ナレッジベース",
"input.new.context": "コンテキストをクリア {{Command}}",
"input.new_topic": "新しいトピック {{Command}}",
"input.pause": "一時停止",
@ -85,19 +97,20 @@
"input.upload": "画像またはドキュメントをアップロード",
"input.upload.document": "ドキュメントをアップロード(モデルは画像をサポートしません)",
"input.web_search": "ウェブ検索を有効にする",
"input.knowledge_base": "ナレッジベース",
"input.file_not_supported": "モデルはこのファイルタイプをサポートしません",
"message.new.branch": "新しいブランチ",
"message.new.branch.created": "新しいブランチが作成されました",
"message.regenerate.model": "モデルを切り替え",
"message.new.context": "新しいコンテキスト",
"message.regenerate.model": "モデルを切り替え",
"message.useful": "役立つ",
"resend": "再送信",
"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.max_tokens.tip": "モデルが生成できる最大トークン数。モデルのコンテキスト制限に基づいて設定する必要があります。そうしないとエラーが発生します",
"settings.reset": "リセット",
"settings.set_as_default": "デフォルトのアシスタントに適用",
"settings.show_line_numbers": "コードに行番号を表示",
@ -105,7 +118,10 @@
"settings.temperature.tip": "低い値はモデルをより創造的で予測不可能にし、高い値はより決定論的で正確にします",
"settings.top_p": "Top-P",
"settings.top_p.tip": "デフォルト値は1で、値が小さいほど回答の多様性が減り、理解しやすくなります。値が大きいほど、AIの語彙範囲が広がり、多様性が増します",
"settings.max_tokens.confirm": "最大トークン制限を有効にする",
"settings.max_tokens.confirm_content": "最大トークン制限を有効にすると、モデルが生成できる最大トークン数が制限されます。これにより、返される結果の長さに影響が出る可能性があります。モデルのコンテキスト制限に基づいて設定する必要があります。そうしないとエラーが発生します",
"suggestions.title": "提案された質問",
"thinking": "思考中...",
"topics.auto_rename": "自動リネーム",
"topics.clear.title": "メッセージをクリア",
"topics.edit.placeholder": "新しい名前を入力",
@ -118,25 +134,26 @@
"topics.list": "トピックリスト",
"topics.move_to": "移動先",
"topics.pinned": "トピックを固定",
"topics.unpinned": "固定解除",
"topics.title": "トピック",
"topics.unpinned": "固定解除",
"translate": "翻訳",
"resend": "再送信",
"thinking": "思考中...",
"deeply_thought": "深く考えています({{secounds}} 秒)"
"topics.prompt": "トピック提示語",
"topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供",
"topics.prompt.edit.title": "トピック提示語を編集する"
},
"common": {
"add": "追加",
"and": "と",
"assistant": "アシスタント",
"avatar": "アバター",
"back": "戻る",
"cancel": "キャンセル",
"chat": "チャット",
"clear": "クリア",
"close": "閉じる",
"copy": "コピー",
"cut": "切り取り",
"default": "デフォルト",
"knowledge_base": "ナレッジベース",
"delete": "削除",
"description": "説明",
"docs": "ドキュメント",
@ -144,6 +161,7 @@
"duplicate": "複製",
"edit": "編集",
"footnotes": "脚注",
"knowledge_base": "ナレッジベース",
"language": "言語",
"model": "モデル",
"models": "モデル",
@ -160,18 +178,11 @@
"topics": "トピック",
"warning": "警告",
"you": "あなた",
"clear": "クリア",
"add": "追加"
"footnote": "引用内容"
},
"error": {
"backup.file_format": "バックアップファイルの形式エラー",
"chat.response": "エラーが発生しました。APIキーが設定されていない場合は、設定 > プロバイダーでキーを設定してください",
"no_api_key": "APIキーが設定されていません",
"provider_disabled": "モデルプロバイダーが有効になっていません",
"render": {
"title": "レンダリングエラー",
"description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください"
},
"http": {
"400": "リクエストに失敗しました。リクエストパラメータが正しいか確認してください。モデルの設定を変更した場合は、デフォルトの設定にリセットしてください",
"401": "認証に失敗しました。APIキーが正しいか確認してください",
@ -182,6 +193,13 @@
"502": "ゲートウェイエラーが発生しました。後でもう一度試してください",
"503": "サービスが利用できません。後でもう一度試してください",
"504": "ゲートウェイタイムアウトが発生しました。後でもう一度試してください"
},
"model.exists": "モデルが既に存在します",
"no_api_key": "APIキーが設定されていません",
"provider_disabled": "モデルプロバイダーが有効になっていません",
"render": {
"description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください",
"title": "レンダリングエラー"
}
},
"export": {
@ -199,20 +217,20 @@
"all": "すべてのファイル",
"count": "数",
"created_at": "作成日",
"delete": "削除",
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
"delete.paintings.warning": "画像に含まれているため、削除できません",
"delete.title": "ファイルを削除",
"document": "ドキュメント",
"edit": "編集",
"file": "ファイル",
"image": "画像",
"name": "名前",
"open": "開く",
"size": "サイズ",
"type": "タイプ",
"text": "テキスト",
"title": "ファイル",
"edit": "編集",
"delete": "削除",
"delete.title": "ファイルを削除",
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
"delete.paintings.warning": "画像に含まれているため、削除できません"
"type": "タイプ"
},
"history": {
"continue_chat": "チャットを続ける",
@ -222,6 +240,69 @@
"search.topics.empty": "トピックが見つかりませんでした。Enterキーを押してすべてのメッセージを検索",
"title": "トピック検索"
},
"knowledge": {
"add": {
"title": "ナレッジベースを追加"
},
"add_directory": "ディレクトリを追加",
"add_file": "ファイルを追加",
"add_note": "ノートを追加",
"add_sitemap": "サイトマップを追加",
"add_url": "URLを追加",
"cancel_index": "インデックスをキャンセル",
"chunk_overlap": "チャンクの重なり",
"chunk_overlap_placeholder": "デフォルト(変更しないでください)",
"chunk_overlap_tooltip": "隣接するチャンク間の重複内容量。チャンク間のコンテキスト関連性を確保し、長文テキストの処理効果を向上させます。",
"chunk_size": "チャンクサイズ",
"chunk_size_change_warning": "チャンクサイズと重複サイズの変更は、新しく追加された内容にのみ適用されます",
"chunk_size_placeholder": "デフォルト(変更しないでください)",
"chunk_size_too_large": "チャンクサイズはモデルのコンテキスト制限を超えることはできません({{max_context}}",
"chunk_size_tooltip": "ドキュメントを分割し、各チャンクのサイズ。モデルのコンテキスト制限を超えないようにしてください。",
"clear_selection": "選択をクリア",
"delete": "削除",
"delete_confirm": "このナレッジベースを削除してもよろしいですか?",
"directories": "ディレクトリ",
"directory_placeholder": "ディレクトリパスを入力",
"document_count": "要求されたドキュメント分段数",
"document_count_default": "デフォルト",
"document_count_help": "要求されたドキュメント分段数が多いほど、付随する情報が多くなりますが、トークンの消費量も増加します",
"drag_file": "ファイルをここにドラッグ",
"empty": "ナレッジベースが見つかりません",
"file_hint": "{{file_types}} 形式をサポート",
"index_all": "すべてをインデックス",
"index_cancelled": "インデックスがキャンセルされました",
"index_started": "インデックスを開始",
"invalid_url": "無効なURL",
"model_info": "モデル情報",
"no_bases": "ナレッジベースがありません",
"no_provider": "ナレッジベースモデルプロバイダーが設定されていません。ナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
"not_set": "未設定",
"not_support": "ナレッジベースデータベースエンジンが更新されました。このナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
"notes": "ノート",
"notes_placeholder": "このナレッジベースの追加情報やコンテキストを入力...",
"rename": "名前を変更",
"search": "ナレッジベースを検索",
"search_placeholder": "検索するテキストを入力",
"settings": "ナレッジベース設定",
"sitemap_placeholder": "サイトマップURLを入力",
"sitemaps": "サイトマップ",
"source": "ソース",
"status": "状態",
"status_completed": "完了",
"status_failed": "失敗",
"status_new": "追加済み",
"status_pending": "保留中",
"status_processing": "処理中",
"title": "ナレッジベース",
"url_added": "URLが追加されました",
"url_placeholder": "URLを入力, 複数のURLはEnterで区切る",
"urls": "URL",
"threshold_tooltip": "ユーザーの質問と知識ベースの内容の関連性を評価するためのしきい値0-1",
"threshold_placeholder": "未設置",
"threshold_too_large_or_small": "しきい値は0より大きく1より小さい必要があります",
"no_match": "知識ベースの内容が見つかりませんでした。",
"threshold": "マッチング度閾値"
},
"languages": {
"arabic": "アラビア語",
"chinese": "中国語",
@ -251,60 +332,114 @@
"title": "Mermaid図"
},
"message": {
"api.check.model.title": "検出に使用するモデルを選択してください",
"api.connection.failed": "接続に失敗しました",
"api.connection.success": "接続に成功しました",
"api.check.model.title": "検出に使用するモデルを選択してください",
"assistant.added.content": "アシスタントが追加されました",
"backup.failed": "バックアップに失敗しました",
"backup.success": "バックアップに成功しました",
"backup.start.success": "バックアップを開始しました",
"backup.success": "バックアップに成功しました",
"chat.completion.paused": "チャットの完了が一時停止されました",
"citations": "参考文献",
"copied": "コピーしました!",
"copy.success": "コピーしました!",
"error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません",
"error.enter.api.host": "APIホストを入力してください",
"error.enter.api.key": "APIキーを入力してください",
"error.enter.model": "モデルを選択してください",
"error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません",
"error.get_embedding_dimensions": "埋込み次元を取得できませんでした",
"error.invalid.enter.model": "モデルを選択してください",
"error.invalid.proxy.url": "無効なプロキシURL",
"error.invalid.webdav": "無効なWebDAV設定",
"error.invalid.enter.api.host": "APIホストを入力してください",
"error.invalid.enter.api.key": "APIキーを入力してください",
"error.invalid.enter.model": "モデルを選択してください",
"error.notion.export": "Notion インポートに失敗",
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
"group.delete.title": "分組メッセージを削除",
"mention.title": "モデルを切り替える",
"message.code_style": "コードスタイル",
"message.delete.content": "このメッセージを削除してもよろしいですか?",
"message.delete.title": "メッセージを削除",
"message.multi_model_style": "複数モデル回答スタイル",
"message.multi_model_style.fold": "折りたたむ",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.style": "メッセージスタイル",
"message.style.bubble": "バブル",
"message.style.plain": "プレーン",
"message.multi_model_style": "複数モデル回答スタイル",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.fold": "折りたたむ",
"regenerate.confirm": "再生成すると現在のメッセージが置き換えられます",
"reset.confirm.content": "すべてのデータをリセットしてもよろしいですか?",
"reset.double.confirm.content": "すべてのデータが失われます。続行しますか?",
"reset.double.confirm.title": "データが失われます!!!",
"restore.success": "復元に成功しました",
"save.success.title": "保存に成功しました",
"success.notion.export": "Notion へのインポートに成功",
"switch.disabled": "現在の応答が完了するまで切り替えを無効にします",
"topic.added": "新しいトピックが追加されました",
"upgrade.success.button": "再起動",
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
"upgrade.success.title": "アップグレードに成功しました",
"regenerate.confirm": "再生成すると現在のメッセージが置き換えられます",
"copy.success": "コピーしました!",
"error.get_embedding_dimensions": "埋込み次元を取得できませんでした",
"group.delete.title": "分組メッセージを削除",
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
"mention.title": "モデルを切り替える",
"error.notion.export": "Notion インポートに失敗",
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"success.notion.export": "Notion へのインポートに成功",
"warn.notion.exporting": "Notion 正在インポート中です。重複インポートしないでください。",
"citations": "参考文献"
"error.enter.name": "ナレッジベース名を入力してください",
"error.invalid.api.host": "無効なAPIアドレスです",
"error.invalid.api.key": "無効なAPIキーです"
},
"minapp": {
"title": "ミニアプリ",
"sidebar.add.title": "サイドバーに追加",
"sidebar.remove.title": "サイドバーから削除"
"sidebar.remove.title": "サイドバーから削除",
"title": "ミニアプリ"
},
"miniwindow": {
"clipboard": {
"empty": "クリップボードが空です"
},
"feature": {
"chat": "この質問に回答",
"explanation": "説明",
"summary": "内容要約",
"translate": "テキスト翻訳"
},
"footer": {
"copy_last_message": "C キーを押してコピー",
"esc": "ESC キーを押して{{action}}",
"esc_back": "戻る",
"esc_close": "ウィンドウを閉じる"
},
"input": {
"placeholder": {
"empty": "{{model}} に質問してください...",
"title": "下のテキストに対して何をしますか?"
}
}
},
"models": {
"add_parameter": "パラメータを追加",
"all": "すべて",
"custom_parameters": "カスタムパラメータ",
"dimensions": "{{dimensions}} 次元",
"embedding": "埋め込み",
"embedding_model": "埋め込み模型",
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
"free": "無料",
"parameter_name": "パラメータ名",
"parameter_type": {
"boolean": "真偽値",
"json": "JSON",
"number": "数値",
"string": "テキスト"
},
"pinned": "固定済み",
"reasoning": "推論",
"search": "モデルを検索...",
"stream_output": "ストリーム出力",
"type": {
"embedding": "埋め込み",
"reasoning": "推論",
"select": "モデルタイプを選択",
"text": "テキスト",
"vision": "画像"
},
"vision": "画像",
"websearch": "ウェブ検索"
},
"ollama": {
"keep_alive_time.description": "モデルがメモリに保持される時間デフォルト5分",
@ -312,6 +447,12 @@
"keep_alive_time.title": "保持時間",
"title": "Ollama"
},
"lmstudio": {
"keep_alive_time.description": "モデルがメモリに保持される時間デフォルト5分",
"keep_alive_time.placeholder": "分",
"keep_alive_time.title": "保持時間",
"title": "LM Studio"
},
"paintings": {
"button.delete.image": "画像を削除",
"button.delete.image.confirm": "この画像を削除してもよろしいですか?",
@ -325,25 +466,35 @@
"negative_prompt_tip": "画像に含めたくない内容を説明します",
"number_images": "生成数",
"number_images_tip": "生成する画像の数1-4",
"prompt_enhancement": "プロンプト強化",
"prompt_enhancement_tip": "オンにすると、プロンプトを詳細でモデルに適したバージョンに書き直します",
"prompt_placeholder": "作成したい画像を説明します。例:夕日の湖畔、遠くに山々",
"regenerate.confirm": "これにより、既存の生成画像が置き換えられます。続行しますか?",
"seed": "シード",
"seed_tip": "同じシードとプロンプトで似た画像を生成できます",
"title": "画像",
"prompt_enhancement": "プロンプト強化",
"prompt_enhancement_tip": "オンにすると、プロンプトを詳細でモデルに適したバージョンに書き直します"
"title": "画像"
},
"prompts": {
"explanation": "この概念を説明してください",
"summarize": "このテキストを要約してください",
"title": "あなたは会話を得意とするアシスタントです。ユーザーの会話を10文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。"
},
"provider": {
"infini": "Infini",
"perplexity": "Perplexity",
"dmxapi": "DMXAPI",
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baidu-cloud": "Baidu Cloud",
"baichuan": "百川",
"baidu-cloud": "Baidu Cloud",
"dashscope": "Alibaba Cloud",
"modelscope": "ModelScope",
"deepseek": "DeepSeek",
"doubao": "豆包",
"doubao": "Volcengine",
"fireworks": "Fireworks",
"gemini": "Gemini",
"gitee-ai": "Gitee AI",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
@ -357,21 +508,23 @@
"nvidia": "NVIDIA",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"lmstudio": "LM Studio",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"qwenlm": "QwenLM",
"silicon": "SiliconFlow",
"stepfun": "StepFun",
"together": "Together",
"yi": "零一万物",
"zhinao": "360智脳",
"zhipu": "智譜AI",
"qwenlm": "QwenLM"
"ppio": "PPIO パイオウクラウド"
},
"settings": {
"about": "について",
"about.checkingUpdate": "更新を確認中...",
"about.checkUpdate": "更新を確認",
"about.checkUpdate.available": "今すぐ更新",
"about.checkingUpdate": "更新を確認中...",
"about.contact.button": "メール",
"about.contact.title": "連絡先",
"about.description": "クリエイターのための強力なAIアシスタント",
@ -382,13 +535,13 @@
"about.license.title": "ライセンス",
"about.releases.button": "リリース",
"about.releases.title": "リリースノート",
"about.social.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": "デフォルトアシスタント",
@ -405,39 +558,61 @@
"title": "キャッシュをクリア"
},
"data.title": "データディレクトリ",
"notion.api_key": "Notion APIキー",
"notion.database_id": "Notion データベースID",
"notion.title": "Notion 設定",
"notion.check": {
"button": "確認",
"fail": "接続に失敗しました。設定を確認してください。",
"success": "接続に成功しました。",
"error": "接続エラーが発生しました。ネットワークを確認してください。",
"empty_api_key": "Api_keyが設定されていません",
"empty_database_id": "Database_idが設定されていません"
},
"title": "データ設定",
"webdav.autoSync": "自動バックアップ",
"webdav.autoSync.off": "オフ",
"webdav.backup.button": "WebDAVにバックアップ",
"webdav.host": "WebDAVホスト",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.hours": "時間",
"webdav.lastSync": "最終同期",
"webdav.minutes": "分",
"webdav.noSync": "次回のバックアップを待っています",
"webdav.password": "WebDAVパスワード",
"webdav.path": "WebDAVパス",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "自動バックアップ",
"webdav.minutes": "分",
"webdav.hours": "時間",
"webdav.restore.button": "WebDAVから復元",
"webdav.title": "WebDAV",
"webdav.user": "WebDAVユーザー",
"webdav.syncStatus": "バックアップ状態",
"webdav.autoSync.off": "オフ",
"webdav.noSync": "次回のバックアップを待っています",
"webdav.restore.content": "WebDAVから復元すると、現在のデータが上書きされます。続行しますか",
"webdav.restore.title": "WebDAVから復元",
"webdav.syncError": "バックアップエラー",
"webdav.lastSync": "最終同期",
"notion.api_key":"Notion APIキー",
"notion.database_id":"Notion データベースID",
"notion.title":"Notion 設定"
},
"quickAssistant": {
"title": "クイックアシスタント",
"click_tray_to_show": "トレイアイコンをクリックして起動",
"enable_quick_assistant": "クイックアシスタントを有効にする",
"use_shortcut_to_show": "トレイアイコンを右クリックするか、ショートカットキーで起動できます"
"webdav.syncStatus": "バックアップ状態",
"webdav.title": "WebDAV",
"webdav.user": "WebDAVユーザー"
},
"display.custom.css": "カスタムCSS",
"display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */",
"display.minApp.disabled": "非表示ミニプログラム",
"display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします",
"display.minApp.title": "ミニプログラム表示設定",
"display.minApp.visible": "表示中ミニプログラム",
"display.sidebar.chat.hiddenMessage": "アシスタントは基本的な機能であり、非表示はサポートされていません",
"display.sidebar.disabled": "アイコンを非表示",
"display.sidebar.empty": "非表示にする機能を左側からここにドラッグ",
"display.sidebar.files.icon": "ファイルのアイコンを表示",
"display.sidebar.knowledge.icon": "ナレッジのアイコンを表示",
"display.sidebar.minapp.icon": "ミニアプリのアイコンを表示",
"display.sidebar.painting.icon": "絵画のアイコンを表示",
"display.sidebar.title": "サイドバー設定",
"display.sidebar.translate.icon": "翻訳のアイコンを表示",
"display.sidebar.visible": "アイコンを表示",
"display.title": "表示設定",
"display.topic.title": "トピック設定",
"font_size.title": "メッセージのフォントサイズ",
"general": "一般設定",
"general.backup.button": "バックアップ",
"general.backup.title": "データのバックアップと復元",
"general.display.title": "表示設定",
"general.manually_check_update.title": "更新チェックを無効にする",
"general.reset.button": "リセット",
"general.reset.title": "データをリセット",
@ -446,24 +621,6 @@
"general.user_name": "ユーザー名",
"general.user_name.placeholder": "ユーザー名を入力",
"general.view_webdav_settings": "WebDAV設定を表示",
"general.display.title": "表示設定",
"display.sidebar.translate.icon": "翻訳のアイコンを表示",
"display.sidebar.painting.icon": "絵画のアイコンを表示",
"display.sidebar.minapp.icon": "ミニアプリのアイコンを表示",
"display.sidebar.knowledge.icon": "ナレッジのアイコンを表示",
"display.sidebar.files.icon": "ファイルのアイコンを表示",
"display.sidebar.title": "サイドバー設定",
"display.sidebar.visible": "アイコンを表示",
"display.sidebar.disabled": "アイコンを非表示",
"display.sidebar.chat.hiddenMessage": "アシスタントは基本的な機能であり、非表示はサポートされていません",
"display.sidebar.empty": "非表示にする機能を左側からここにドラッグ",
"display.topic.title": "トピック設定",
"display.custom.css": "カスタムCSS",
"display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */",
"display.minApp.title": "ミニプログラム表示設定",
"display.minApp.visible": "表示中ミニプログラム",
"display.minApp.disabled": "非表示ミニプログラム",
"display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします",
"input.auto_translate_with_space": "スペースを3回押して翻訳",
"input.target_language": "目標言語",
"input.target_language.chinese": "簡体字中国語",
@ -473,16 +630,16 @@
"input.target_language.russian": "ロシア語",
"messages.divider": "メッセージ間に区切り線を表示",
"messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け",
"messages.input.paste_long_text_threshold": "長いテキストの長さ",
"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.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
"messages.model.title": "モデル設定",
"messages.title": "メッセージ設定",
"messages.use_serif_font": "セリフフォントを使用",
"messages.input.paste_long_text_threshold": "長いテキストの長さ",
"model": "デフォルトモデル",
"models.add.add_model": "モデルを追加",
"models.add.group_name": "グループ名",
@ -496,15 +653,15 @@
"models.default_assistant_model": "デフォルトアシスタントモデル",
"models.default_assistant_model_description": "新しいアシスタントを作成する際に使用されるモデル。アシスタントがモデルを設定していない場合、このモデルが使用されます",
"models.empty": "モデルが見つかりません",
"models.enable_topic_naming": "トピックの自動命名",
"models.topic_naming_model": "トピック命名モデル",
"models.topic_naming_model_description": "新しいトピックを自動的に命名する際に使用されるモデル",
"models.topic_naming_model_setting_title": "トピック命名モデルの設定",
"models.topic_naming_prompt": "トピック命名プロンプト",
"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",
@ -517,6 +674,7 @@
"api_key": "APIキー",
"api_key.tip": "複数のキーはカンマで区切ります",
"api_version": "APIバージョン",
"charge": "充電",
"check": "チェック",
"check_all_keys": "すべてのキーをチェック",
"check_multiple_keys": "複数のAPIキーをチェック",
@ -525,7 +683,6 @@
"docs_check": "チェック",
"docs_more_details": "詳細を確認",
"get_api_key": "APIキーを取得",
"charge": "充電",
"no_models": "API接続をチェックする前に、モデルを追加してください",
"not_checked": "未チェック",
"remove_duplicate_keys": "重複キーを削除",
@ -543,28 +700,34 @@
"title": "プロキシ設定"
},
"proxy.title": "プロキシアドレス",
"quickAssistant": {
"click_tray_to_show": "トレイアイコンをクリックして起動",
"enable_quick_assistant": "クイックアシスタントを有効にする",
"title": "クイックアシスタント",
"use_shortcut_to_show": "トレイアイコンを右クリックするか、ショートカットキーで起動できます"
},
"shortcuts": {
"action": "操作",
"alt_warning": "MacではOption + 文字をショートカットとして使用できません",
"clear_shortcut": "ショートカットをクリア",
"clear_topic": "メッセージを消去",
"copy_last_message": "最後のメッセージをコピー",
"key": "キー",
"mini_window": "クイックアシスタント",
"new_topic": "新しいトピック",
"title": "ショートカット",
"zoom_in": "ズームイン",
"zoom_out": "ズームアウト",
"zoom_reset": "ズームをリセット",
"show_app": "アプリを表示",
"press_shortcut": "ショートカットを押す",
"reset_defaults": "デフォルトのショートカットをリセット",
"reset_defaults_confirm": "すべてのショートカットをリセットしてもよろしいですか?",
"press_shortcut": "ショートカットを押す",
"alt_warning": "MacではOption + 文字をショートカットとして使用できません",
"reset_to_default": "デフォルトにリセット",
"clear_shortcut": "ショートカットをクリア",
"search_message": "メッセージを検索",
"show_app": "アプリを表示",
"title": "ショートカット",
"toggle_new_context": "コンテキストをクリア",
"toggle_show_assistants": "アシスタントの表示を切り替え",
"toggle_show_topics": "トピックの表示を切り替え",
"copy_last_message": "最後のメッセージをコピー",
"search_message": "メッセージを検索",
"mini_window": "クイックアシスタント",
"clear_topic": "メッセージを消去",
"toggle_new_context": "コンテキストをクリア"
"zoom_in": "ズームイン",
"zoom_out": "ズームアウト",
"zoom_reset": "ズームをリセット"
},
"theme.auto": "自動",
"theme.dark": "ダークテーマ",
@ -583,151 +746,31 @@
"translate": {
"any.language": "任意の言語",
"button.translate": "翻訳",
"close": "閉じる",
"confirm": {
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
"title": "翻訳確認"
},
"error.not_configured": "翻訳モデルが設定されていません",
"error.failed": "翻訳に失敗しました",
"error.not_configured": "翻訳モデルが設定されていません",
"input.placeholder": "翻訳するテキストを入力",
"output.placeholder": "翻訳",
"processing": "翻訳中...",
"title": "翻訳",
"close": "閉じる"
"title": "翻訳"
},
"tray": {
"quit": "終了",
"show_window": "ウィンドウを表示",
"show_mini_window": "クイックアシスタント"
"show_mini_window": "クイックアシスタント",
"show_window": "ウィンドウを表示"
},
"words": {
"knowledgeGraph": "ナレッジグラフ",
"visualization": "可視化",
"quit": "終了",
"show_window": "ウィンドウを表示",
"quit": "終了"
"visualization": "可視化"
},
"knowledge": {
"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": "ナレッジベースデータベースエンジンが更新されました。このナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
"no_provider": "ナレッジベースモデルプロバイダーが設定されていません。ナレッジベースはもうサポートされていません。新しいナレッジベースを作成してください",
"source": "ソース",
"chunk_size": "チャンクサイズ",
"chunk_overlap": "チャンクの重なり",
"not_set": "未設定",
"settings": "ナレッジベース設定",
"document_count": "要求されたドキュメント数",
"document_count_help": "要求されたドキュメント数が多いほど、付随する情報が多くなりますが、トークンの消費量も増加します",
"document_count_default": "デフォルト",
"chunk_size_placeholder": "デフォルト(変更しないでください)",
"chunk_overlap_placeholder": "デフォルト(変更しないでください)",
"chunk_size_tooltip": "ドキュメントを分割し、各チャンクのサイズ。モデルのコンテキスト制限を超えないようにしてください。",
"chunk_overlap_tooltip": "隣接するチャンク間の重複内容量。チャンク間のコンテキスト関連性を確保し、長文テキストの処理効果を向上させます。",
"chunk_size_change_warning": "チャンクサイズと重複サイズの変更は、新しく追加された内容にのみ適用されます",
"chunk_size_too_large": "チャンクサイズはモデルのコンテキスト制限を超えることはできません({{max_context}}"
},
"models": {
"pinned": "固定済み",
"search": "モデルを検索...",
"stream_output": "ストリーム出力",
"type": {
"select": "モデルタイプを選択",
"text": "テキスト",
"vision": "画像",
"embedding": "埋め込み",
"reasoning": "推論"
},
"all": "すべて",
"vision": "画像",
"websearch": "ウェブ検索",
"free": "無料",
"reasoning": "推論",
"embedding": "埋め込み",
"embedding_model": "埋め込み模型",
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
"dimensions": "{{dimensions}} 次元",
"custom_parameters": "カスタムパラメータ",
"add_parameter": "パラメータを追加",
"parameter_name": "パラメータ名",
"parameter_type": {
"string": "テキスト",
"number": "数値",
"boolean": "真偽値",
"json": "JSON"
}
},
"prompts": {
"title": "あなたは会話を得意とするアシスタントです。ユーザーの会話を10文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。",
"explanation": "この概念を説明してください",
"summarize": "このテキストを要約してください"
},
"miniwindow": {
"feature": {
"chat": "この質問に回答",
"translate": "テキスト翻訳",
"summary": "内容要約",
"explanation": "説明"
},
"clipboard": {
"empty": "クリップボードが空です"
},
"input": {
"placeholder": {
"title": "下のテキストに対して何をしますか?",
"empty": "{{model}} に質問してください..."
}
},
"footer": {
"esc": "ESC キーを押して{{action}}",
"esc_close": "ウィンドウを閉じる",
"esc_back": "戻る",
"copy_last_message": "C キーを押してコピー"
}
},
"auth": {
"oauth_button": "{{provider}}で認証",
"get_key": "取得",
"get_key_success": "APIキーの自動取得に成功しました",
"login": "認証",
"error": "APIキーの自動取得に失敗しました。手動で取得してください"
"docs": {
"title": "ドキュメント"
}
}
}

View File

@ -2,12 +2,12 @@
"translation": {
"agents": {
"add.button": "Добавить к ассистенту",
"add.knowledge_base": "База знаний",
"add.knowledge_base.placeholder": "Выберите базу знаний",
"add.name": "Имя",
"add.name.placeholder": "Введите имя",
"add.prompt": "Промпт",
"add.prompt.placeholder": "Введите промпт",
"add.knowledge_base": "База знаний",
"add.knowledge_base.placeholder": "Выберите базу знаний",
"add.title": "Создать агента",
"delete.popup.content": "Вы уверены, что хотите удалить этого агента?",
"edit.message.add.title": "Добавить",
@ -42,14 +42,24 @@
"save.success": "Успешно сохранено",
"save.title": "Сохранить в агента",
"search": "Поиск ассистентов...",
"settings.auto_reset_model": "Автосброс модели",
"settings.auto_reset_model.tip": "Автоматически сбрасывать модель при создании нового топика.",
"settings.default_model": "Модель по умолчанию",
"settings.knowledge_base": "Настройки базы знаний",
"settings.model": "Настройки модели",
"settings.preset_messages": "Предустановленные сообщения",
"settings.prompt": "Настройки промптов",
"settings.knowledge_base": "Настройки базы знаний",
"title": "Ассистенты"
"title": "Ассистенты",
"settings.reasoning_effort": "Длина цепочки рассуждений",
"settings.reasoning_effort.high": "Длинная",
"settings.reasoning_effort.low": "Короткая",
"settings.reasoning_effort.medium": "Средняя",
"settings.reasoning_effort.tip": "Эта настройка поддерживается только моделями с рассуждением"
},
"auth": {
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную",
"get_key": "Получить",
"get_key_success": "Автоматический получение ключа API успешно",
"login": "Войти",
"oauth_button": "Авторизоваться с {{provider}}"
},
"button": {
"add": "Добавить",
@ -64,6 +74,7 @@
"artifacts.button.download": "Скачать",
"artifacts.button.preview": "Предпросмотр",
"assistant.search.placeholder": "Поиск",
"deeply_thought": "Мыслим ({{secounds}} секунд)",
"default.description": "Привет, я Ассистент по умолчанию. Вы можете начать общаться со мной прямо сейчас",
"default.name": "⭐️ Ассистент по умолчанию",
"default.topic.name": "Топик по умолчанию",
@ -74,6 +85,7 @@
"input.context_count.tip": "Количество контекстов",
"input.estimated_tokens.tip": "Затраты токенов",
"input.expand": "Развернуть",
"input.knowledge_base": "База знаний",
"input.new.context": "Очистить контекст {{Command}}",
"input.new_topic": "Новый топик {{Command}}",
"input.pause": "Остановить",
@ -85,19 +97,20 @@
"input.upload": "Загрузить изображение или документ",
"input.upload.document": "Загрузить документ (модель не поддерживает изображения)",
"input.web_search": "Включить веб-поиск",
"input.knowledge_base": "База знаний",
"input.file_not_supported": "Модель не поддерживает этот тип файла",
"message.new.branch": "Новая ветка",
"message.new.branch.created": "Новая ветка создана",
"message.regenerate.model": "Переключить модель",
"message.new.context": "Новый контекст",
"message.regenerate.model": "Переключить модель",
"message.useful": "Полезно",
"resend": "Переотправить",
"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.max_tokens.tip": "Максимальное количество токенов, которые может сгенерировать модель. Нужно учитывать контекст модели, иначе будет ошибка",
"settings.reset": "Сбросить",
"settings.set_as_default": "Применить к ассистенту по умолчанию",
"settings.show_line_numbers": "Показать номера строк в коде",
@ -105,7 +118,10 @@
"settings.temperature.tip": "Меньшие значения делают модель более креативной и непредсказуемой, в то время как большие значения делают её более детерминированной и точной.",
"settings.top_p": "Top-P",
"settings.top_p.tip": "Значение по умолчанию 1, чем меньше значение, тем меньше вариативности в ответах, тем проще понять, чем больше значение, тем больше вариативности в ответах, тем больше разнообразие",
"settings.max_tokens.confirm": "Включить лимит максимальных токенов",
"settings.max_tokens.confirm_content": "Включить лимит максимальных токенов, влияет на длину результата. Нужно учитывать контекст модели, иначе будет ошибка",
"suggestions.title": "Предложенные вопросы",
"thinking": "Мыслим",
"topics.auto_rename": "Автопереименование",
"topics.clear.title": "Очистить сообщения",
"topics.edit.placeholder": "Введите новый заголовок",
@ -118,25 +134,26 @@
"topics.list": "Список топиков",
"topics.move_to": "Переместить в",
"topics.pinned": "Закрепленные темы",
"topics.unpinned": "Открепленные темы",
"topics.title": "Топики",
"topics.unpinned": "Открепленные темы",
"translate": "Перевести",
"resend": "Переотправить",
"thinking": "Мыслим",
"deeply_thought": "Мыслим ({{secounds}} секунд)"
"topics.prompt": "Тематические подсказки",
"topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы",
"topics.prompt.edit.title": "Редактировать подсказки темы"
},
"common": {
"add": "Добавить",
"and": "и",
"assistant": "Ассистент",
"avatar": "Аватар",
"back": "Назад",
"cancel": "Отмена",
"chat": "Чат",
"clear": "Очистить",
"close": "Закрыть",
"copy": "Копировать",
"cut": "Вырезать",
"default": "По умолчанию",
"knowledge_base": "База знаний",
"delete": "Удалить",
"description": "Описание",
"docs": "Документы",
@ -144,6 +161,7 @@
"duplicate": "Дублировать",
"edit": "Редактировать",
"footnotes": "Сноски",
"knowledge_base": "База знаний",
"language": "Язык",
"model": "Модель",
"models": "Модели",
@ -160,18 +178,11 @@
"topics": "Топики",
"warning": "Предупреждение",
"you": "Вы",
"clear": "Очистить",
"add": "Добавить"
"footnote": "Цитируемый контент"
},
"error": {
"backup.file_format": "Ошибка формата файла резервной копии",
"chat.response": "Что-то пошло не так. Пожалуйста, проверьте, установлен ли ваш ключ API в Настройки > Провайдеры",
"no_api_key": "Ключ API не настроен",
"provider_disabled": "Провайдер моделей не включен",
"render": {
"title": "Ошибка рендеринга",
"description": "Не удалось рендерить формулу. Пожалуйста, проверьте, правильно ли формат формулы"
},
"http": {
"400": "Не удалось выполнить запрос. Пожалуйста, проверьте, правильно ли настроены параметры запроса. Если вы изменили настройки модели, пожалуйста, сбросьте их до значений по умолчанию",
"401": "Не удалось пройти аутентификацию. Пожалуйста, проверьте, правильно ли настроен ваш ключ API",
@ -182,6 +193,13 @@
"502": "Серверная ошибка. Пожалуйста, попробуйте позже",
"503": "Серверная ошибка. Пожалуйста, попробуйте позже",
"504": "Серверная ошибка. Пожалуйста, попробуйте позже"
},
"model.exists": "Модель уже существует",
"no_api_key": "Ключ API не настроен",
"provider_disabled": "Провайдер моделей не включен",
"render": {
"description": "Не удалось рендерить формулу. Пожалуйста, проверьте, правильно ли формат формулы",
"title": "Ошибка рендеринга"
}
},
"export": {
@ -199,20 +217,20 @@
"all": "Все файлы",
"count": "Количество",
"created_at": "Дата создания",
"delete": "Удалить",
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно",
"delete.title": "Удалить файл",
"document": "Документ",
"edit": "Редактировать",
"file": "Файл",
"image": "Изображение",
"name": "Имя",
"open": "Открыть",
"size": "Размер",
"type": "Тип",
"text": "Текст",
"title": "Файлы",
"edit": "Редактировать",
"delete": "Удалить",
"delete.title": "Удалить файл",
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно"
"type": "Тип"
},
"history": {
"continue_chat": "Продолжить чат",
@ -222,6 +240,69 @@
"search.topics.empty": "Топики не найдены, нажмите Enter для поиска всех сообщений",
"title": "Поиск топиков"
},
"knowledge": {
"add": {
"title": "Добавить базу знаний"
},
"add_directory": "Добавить директорию",
"add_file": "Добавить файл",
"add_note": "Добавить запись",
"add_sitemap": "Карта сайта",
"add_url": "Добавить URL",
"cancel_index": "Отменить индексирование",
"chunk_overlap": "Перекрытие фрагмента",
"chunk_overlap_placeholder": "По умолчанию (не рекомендуется изменять)",
"chunk_overlap_tooltip": "Перекрытие фрагмента, не превышающее модель контекста",
"chunk_size": "Размер фрагмента",
"chunk_size_change_warning": "Размер фрагмента и перекрытие фрагмента могут быть изменены только для новых содержимого",
"chunk_size_placeholder": "По умолчанию (не рекомендуется изменять)",
"chunk_size_too_large": "Размер фрагмента не может превышать модель контекста ({{max_context}})",
"chunk_size_tooltip": "Размер фрагмента, не превышающий модель контекста",
"clear_selection": "Очистить выбор",
"delete": "Удалить",
"delete_confirm": "Вы уверены, что хотите удалить эту базу знаний?",
"directories": "Директории",
"directory_placeholder": "Введите путь к директории",
"document_count": "Количество запрошенных документов",
"document_count_default": "По умолчанию",
"document_count_help": "Количество запрошенных документов, вместе с ними передается больше информации, но и требуется больше токенов",
"drag_file": "Перетащите файл сюда",
"empty": "База знаний не найдена",
"file_hint": "Поддерживаются {{file_types}}",
"index_all": "Индексировать все",
"index_cancelled": "Индексирование отменено",
"index_started": "Индексирование началось",
"invalid_url": "Неверный URL",
"model_info": "Модель информации",
"no_bases": "База знаний не найдена",
"no_provider": "База знаний модель поставщика не настроена, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
"not_set": "Не установлено",
"not_support": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
"notes": "Заметки",
"notes_placeholder": "Введите дополнительную информацию или контекст для этой базы знаний...",
"rename": "Переименовать",
"search": "Поиск в базе знаний",
"search_placeholder": "Введите текст для поиска",
"settings": "Настройки базы знаний",
"sitemap_placeholder": "Введите URL карты сайта",
"sitemaps": "Сайты",
"source": "Источник",
"status": "Статус",
"status_completed": "Завершено",
"status_failed": "Ошибка",
"status_new": "Добавлено",
"status_pending": "Ожидание",
"status_processing": "Обработка",
"title": "База знаний",
"url_added": "URL добавлен",
"url_placeholder": "Введите URL, несколько URL через Enter",
"urls": "URL-адреса",
"threshold_tooltip": "Используется для оценки соответствия между пользовательским вопросом и содержимым в базе знаний (0-1)",
"threshold_placeholder": "Не установлено",
"threshold_too_large_or_small": "Порог не может быть больше 1 или меньше 0",
"no_match": "Не найдено содержимого в базе знаний.",
"threshold": "Порог соответствия"
},
"languages": {
"arabic": "Арабский",
"chinese": "Китайский",
@ -251,61 +332,114 @@
"title": "Диаграмма Mermaid"
},
"message": {
"api.check.model.title": "Выберите модель для проверки",
"api.connection.failed": "Соединение не удалось",
"api.connection.success": "Соединение успешно",
"api.check.model.title": "Выберите модель для проверки",
"assistant.added.content": "Ассистент успешно добавлен",
"backup.failed": "Создание резервной копии не удалось",
"backup.success": "Резервная копия успешно создана",
"backup.start.success": "Создание резервной копии начато",
"backup.success": "Резервная копия успешно создана",
"chat.completion.paused": "Завершение чата приостановлено",
"citations": "Источники",
"copied": "Скопировано!",
"copy.success": "Скопировано!",
"error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.",
"error.enter.api.host": "Пожалуйста, введите ваш API хост",
"error.enter.api.key": "Пожалуйста, введите ваш API ключ",
"error.enter.model": "Пожалуйста, выберите модель",
"error.enter.name": "Пожалуйста, введите название базы знаний",
"error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.",
"error.get_embedding_dimensions": "Не удалось получить размерность встраивания",
"error.invalid.enter.model": "Пожалуйста, выберите модель",
"error.invalid.proxy.url": "Неверный URL прокси",
"error.invalid.webdav": "Неверные настройки WebDAV",
"error.invalid.enter.api.host": "Пожалуйста, введите правильный API хост",
"error.invalid.enter.api.key": "Пожалуйста, введите правильный API ключ",
"error.invalid.enter.model": "Пожалуйста, выберите модель",
"error.notion.export": "Импорт в Notion не удался",
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
"group.delete.title": "Удалить группу сообщений",
"mention.title": "Переключить модель ответа",
"message.code_style": "Стиль кода",
"message.delete.content": "Вы уверены, что хотите удалить это сообщение?",
"message.delete.title": "Удалить сообщение",
"message.multi_model_style": "Стиль ответов от нескольких моделей",
"message.multi_model_style.fold": "Свернуть",
"message.multi_model_style.horizontal": "Горизонтальный",
"message.multi_model_style.vertical": "Вертикальный",
"message.style": "Стиль сообщения",
"message.style.bubble": "Пузырь",
"message.style.plain": "Простой",
"message.multi_model_style": "Стиль ответов от нескольких моделей",
"message.multi_model_style.horizontal": "Горизонтальный",
"message.multi_model_style.vertical": "Вертикальный",
"message.multi_model_style.fold": "Свернуть",
"regenerate.confirm": "Перегенерация заменит текущее сообщение",
"reset.confirm.content": "Вы уверены, что хотите очистить все данные?",
"reset.double.confirm.content": "Все данные будут утеряны, хотите продолжить?",
"reset.double.confirm.title": "ДАННЫЕ БУДУТ УТЕРЯНЫ !!!",
"restore.success": "Успешно восстановлено",
"save.success.title": "Успешно сохранено",
"success.notion.export": "Импорт в Notion выполнен успешно",
"switch.disabled": "Пожалуйста, дождитесь завершения текущего ответа",
"topic.added": "Новый топик добавлен",
"upgrade.success.button": "Перезапустить",
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
"upgrade.success.title": "Обновление успешно",
"regenerate.confirm": "Перегенерация заменит текущее сообщение",
"copy.success": "Скопировано!",
"error.get_embedding_dimensions": "Не удалось получить размерность встраивания",
"group.delete.title": "Удалить группу сообщений",
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
"mention.title": "Переключить модель ответа",
"error.notion.export": "Импорт в Notion не удался",
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"success.notion.export": "Импорт в Notion выполнен успешно",
"warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт",
"citations": "Источники"
"error.invalid.api.host": "Неверный API адрес",
"error.invalid.api.key": "Неверный API ключ"
},
"minapp": {
"title": "Встроенные приложения",
"sidebar.add.title": "Добавить в боковую панель",
"sidebar.remove.title": "Удалить из боковой панели"
"sidebar.remove.title": "Удалить из боковой панели",
"title": "Встроенные приложения"
},
"miniwindow": {
"clipboard": {
"empty": "Буфер обмена пуст"
},
"feature": {
"chat": "Ответить на этот вопрос",
"explanation": "Объяснение",
"summary": "Содержание",
"translate": "Текст перевод"
},
"footer": {
"copy_last_message": "Нажмите C для копирования",
"esc": "Нажмите ESC {{action}}",
"esc_back": "возвращения",
"esc_close": "закрытия окна"
},
"input": {
"placeholder": {
"empty": "Задайте вопрос {{model}}...",
"title": "Что вы хотите сделать с этим текстом?"
}
}
},
"models": {
"add_parameter": "Добавить параметр",
"all": "Все",
"custom_parameters": "Пользовательские параметры",
"dimensions": "{{dimensions}} мер",
"embedding": "Встраиваемые",
"embedding_model": "Встраиваемые модели",
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",
"free": "Бесплатные",
"parameter_name": "Имя параметра",
"parameter_type": {
"boolean": "Логическое",
"json": "JSON",
"number": "Число",
"string": "Текст"
},
"pinned": "Закреплено",
"reasoning": "Рассуждение",
"search": "Поиск моделей...",
"stream_output": "Потоковый вывод",
"type": {
"embedding": "Встраиваемые",
"reasoning": "Рассуждение",
"select": "Выберите тип модели",
"text": "Текст",
"vision": "Изображение"
},
"vision": "Визуальные",
"websearch": "Веб-поисковые"
},
"ollama": {
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
@ -313,6 +447,12 @@
"keep_alive_time.title": "Время жизни модели",
"title": "Ollama"
},
"lmstudio": {
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
"keep_alive_time.placeholder": "Минуты",
"keep_alive_time.title": "Время жизни модели",
"title": "LM Studio"
},
"paintings": {
"button.delete.image": "Удалить изображение",
"button.delete.image.confirm": "Вы уверены, что хотите удалить это изображение?",
@ -326,25 +466,35 @@
"negative_prompt_tip": "Опишите, что вы не хотите включать в изображение",
"number_images": "Количество изображений",
"number_images_tip": "Количество изображений для генерации (1-4)",
"prompt_enhancement": "Улучшение промпта",
"prompt_enhancement_tip": "При включении переписывает промпт в более детальную, модель-ориентированную версию",
"prompt_placeholder": "Опишите изображение, которое вы хотите создать, например, Спокойное озеро на закате с горами на заднем плане",
"regenerate.confirm": "Это заменит ваши существующие сгенерированные изображения. Хотите продолжить?",
"seed": "Ключ генерации",
"seed_tip": "Одинаковый ключ генерации и промпт могут производить похожие изображения",
"title": "Изображения",
"prompt_enhancement": "Улучшение промпта",
"prompt_enhancement_tip": "При включении переписывает промпт в более детальную, модель-ориентированную версию"
"title": "Изображения"
},
"prompts": {
"explanation": "Объясните мне этот концепт",
"summarize": "Суммируйте этот текст",
"title": "Вы - эксперт в общении, который суммирует разговоры пользователя в 10-символьном заголовке, совпадающем с языком пользователя, без использования знаков препинания и других специальных символов"
},
"provider": {
"infini": "Infini",
"perplexity": "Perplexity",
"dmxapi": "DMXAPI",
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baidu-cloud": "Baidu Cloud",
"baichuan": "Baichuan",
"baidu-cloud": "Baidu Cloud",
"dashscope": "Alibaba Cloud",
"modelscope": "ModelScope",
"deepseek": "DeepSeek",
"doubao": "Doubao",
"doubao": "Volcengine",
"fireworks": "Fireworks",
"gemini": "Gemini",
"gitee-ai": "Gitee AI",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
@ -358,21 +508,23 @@
"nvidia": "Nvidia",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"lmstudio": "LM Studio",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"qwenlm": "QwenLM",
"silicon": "SiliconFlow",
"stepfun": "StepFun",
"together": "Together",
"yi": "Yi",
"zhinao": "360AI",
"zhipu": "ZHIPU AI",
"qwenlm": "QwenLM"
"ppio": "PPIO"
},
"settings": {
"about": "О программе и обратная связь",
"about.checkingUpdate": "Проверка обновлений...",
"about.checkUpdate": "Проверить обновления",
"about.checkUpdate.available": "Обновить",
"about.checkingUpdate": "Проверка обновлений...",
"about.contact.button": "Электронная почта",
"about.contact.title": "Контакты",
"about.description": "Мощный AI-ассистент для созидания",
@ -383,13 +535,13 @@
"about.license.title": "Лицензия",
"about.releases.button": "Релизы",
"about.releases.title": "Заметки о релизах",
"about.social.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": "Ассистент по умолчанию",
@ -406,39 +558,61 @@
"title": "Очистка кэша"
},
"data.title": "Каталог данных",
"notion.api_key": "Ключ API Notion",
"notion.database_id": "ID базы данных Notion",
"notion.title": "Настройки Notion",
"notion.check": {
"button": "Проверить",
"fail": "Ошибка подключения, проверьте настройки",
"success": "Подключение успешно",
"error": "Ошибка подключения, проверьте сеть",
"empty_api_key": "Не настроен Api_key",
"empty_database_id": "Не настроен Database_id"
},
"title": "Настройки данных",
"webdav.autoSync": "Автоматическое резервное копирование",
"webdav.autoSync.off": "Выключено",
"webdav.backup.button": "Резервное копирование на WebDAV",
"webdav.host": "Хост WebDAV",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.hours": "часов",
"webdav.lastSync": "Последняя синхронизация",
"webdav.minutes": "минут",
"webdav.noSync": "Ожидание следующего резервного копирования",
"webdav.password": "Пароль WebDAV",
"webdav.path": "Путь WebDAV",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "Автоматическое резервное копирование",
"webdav.minutes": "минут",
"webdav.hours": "часов",
"webdav.restore.button": "Восстановление с WebDAV",
"webdav.title": "WebDAV",
"webdav.user": "Пользователь WebDAV",
"webdav.syncStatus": "Статус резервного копирования",
"webdav.autoSync.off": "Выключено",
"webdav.noSync": "Ожидание следующего резервного копирования",
"webdav.restore.content": "Восстановление с WebDAV перезапишет текущие данные, продолжить?",
"webdav.restore.title": "Восстановление с WebDAV",
"webdav.syncError": "Ошибка резервного копирования",
"webdav.lastSync": "Последняя синхронизация",
"notion.api_key":"Ключ API Notion",
"notion.database_id":"ID базы данных Notion",
"notion.title":"Настройки Notion"
},
"quickAssistant": {
"title": "Быстрый помощник",
"click_tray_to_show": "Нажмите на иконку трея для запуска",
"enable_quick_assistant": "Включить быстрый помощник",
"use_shortcut_to_show": "Нажмите на иконку трея или используйте горячие клавиши для запуска"
"webdav.syncStatus": "Статус резервного копирования",
"webdav.title": "WebDAV",
"webdav.user": "Пользователь WebDAV"
},
"display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",
"display.minApp.disabled": "скрытый апплет",
"display.minApp.empty": "Перетащите апплет, который хотите скрыть, слева сюда",
"display.minApp.title": "Настройки отображения мини программы",
"display.minApp.visible": "Отображаемый апплет",
"display.sidebar.chat.hiddenMessage": "Помощник является базовой функцией и не поддерживает скрытие",
"display.sidebar.disabled": "Скрыть иконки",
"display.sidebar.empty": "Перетащите скрываемую функцию с левой стороны сюда",
"display.sidebar.files.icon": "Показывать иконку файлов",
"display.sidebar.knowledge.icon": "Показывать иконку знаний",
"display.sidebar.minapp.icon": "Показывать иконку мини-приложения",
"display.sidebar.painting.icon": "Показывать иконку рисования",
"display.sidebar.title": "Настройки боковой панели",
"display.sidebar.translate.icon": "Показывать иконку перевода",
"display.sidebar.visible": "Показывать иконки",
"display.title": "Настройки отображения",
"display.topic.title": "Настройки топиков",
"font_size.title": "Размер шрифта сообщений",
"general": "Общие настройки",
"general.backup.button": "Резервное копирование",
"general.backup.title": "Резервное копирование и восстановление данных",
"general.display.title": "Настройки отображения",
"general.manually_check_update.title": "Отключить проверку обновлений",
"general.reset.button": "Сброс",
"general.reset.title": "Сброс данных",
@ -447,43 +621,24 @@
"general.user_name": "Имя пользователя",
"general.user_name.placeholder": "Введите ваше имя",
"general.view_webdav_settings": "Просмотр настроек WebDAV",
"general.display.title": "Настройки отображения",
"display.sidebar.translate.icon": "Показывать иконку перевода",
"display.sidebar.painting.icon": "Показывать иконку рисования",
"display.sidebar.minapp.icon": "Показывать иконку мини-приложения",
"display.sidebar.knowledge.icon": "Показывать иконку знаний",
"display.sidebar.files.icon": "Показывать иконку файлов",
"display.sidebar.title": "Настройки боковой панели",
"display.sidebar.visible": "Показывать иконки",
"display.sidebar.disabled": "Скрыть иконки",
"display.sidebar.chat.hiddenMessage": "Помощник является базовой функцией и не поддерживает скрытие",
"display.sidebar.empty": "Перетащите скрываемую функцию с левой стороны сюда",
"display.minApp.title": "Настройки отображения мини программы",
"display.minApp.visible": "Отображаемый апплет",
"display.minApp.disabled": "скрытый апплет",
"display.minApp.empty": "Перетащите апплет, который хотите скрыть, слева сюда",
"display.topic.title": "Настройки топиков",
"display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",
"input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов",
"input.target_language": "Целевой язык",
"input.target_language.chinese": "Китайский упрощенный",
"input.target_language.chinese-traditional": "Китайский традиционный",
"input.target_language.english": "Английский",
"input.target_language.japanese": "Японский",
"input.target_language.russianinput.translate": "Русский",
"messages.divider": "Показывать разделитель между сообщениями",
"messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл",
"messages.input.paste_long_text_threshold": "Длина вставки длинного текста",
"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.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
"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": "Имя группы",
@ -497,15 +652,15 @@
"models.default_assistant_model": "Модель ассистента по умолчанию",
"models.default_assistant_model_description": "Модель, используемая при создании нового ассистента, если ассистент не имеет настроенной модели, будет использоваться эта модель",
"models.empty": "Модели не найдены",
"models.enable_topic_naming": "Автоматическое переименование топика",
"models.topic_naming_model": "Модель именования топика",
"models.topic_naming_model_description": "Модель, используемая при автоматическом именовании нового топика",
"models.topic_naming_model_setting_title": "Настройки модели именования топика",
"models.topic_naming_prompt": "Подсказка для именования топика",
"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",
@ -518,6 +673,7 @@
"api_key": "Ключ API",
"api_key.tip": "Несколько ключей, разделенных запятыми",
"api_version": "Версия API",
"charge": "Пополнить",
"check": "Проверить",
"check_all_keys": "Проверить все ключи",
"check_multiple_keys": "Проверить несколько ключей API",
@ -526,7 +682,6 @@
"docs_check": "Проверить",
"docs_more_details": "для получения дополнительной информации",
"get_api_key": "Получить ключ API",
"charge": "Пополнить",
"no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API",
"not_checked": "Не проверено",
"remove_duplicate_keys": "Удалить дубликаты ключей",
@ -534,17 +689,6 @@
"search_placeholder": "Поиск по ID или имени модели",
"title": "Провайдеры моделей"
},
"provider.api.url.preview": "Предпросмотр: {{url}}",
"provider.api.url.reset": "Сброс",
"provider.api.url.tip": "Заканчивая на / игнорирует v1, заканчивая на # принудительно использует введенный адрес",
"provider.api_host": "Хост API",
"provider.api_key": "Ключ API",
"provider.api_key.tip": "Несколько ключей, разделенных запятыми",
"provider.api_version": "Версия API",
"provider.check": "Проверить",
"provider.docs_check": "Проверить",
"provider.docs_more_details": "для получения дополнительной информации",
"provider.search_placeholder": "Поиск по ID или имени модели",
"proxy": {
"mode": {
"custom": "Пользовательский прокси",
@ -555,28 +699,34 @@
"title": "Настройки прокси"
},
"proxy.title": "Адрес прокси",
"quickAssistant": {
"click_tray_to_show": "Нажмите на иконку трея для запуска",
"enable_quick_assistant": "Включить быстрый помощник",
"title": "Быстрый помощник",
"use_shortcut_to_show": "Нажмите на иконку трея или используйте горячие клавиши для запуска"
},
"shortcuts": {
"action": "Действие",
"alt_warning": "Mac не поддерживает Option + буквы как горячие клавиши",
"clear_shortcut": "Очистить сочетание клавиш",
"clear_topic": "Очистить все сообщения",
"copy_last_message": "Копировать последнее сообщение",
"key": "Клавиша",
"mini_window": "Быстрый помощник",
"new_topic": "Новый топик",
"title": "Горячие клавиши",
"zoom_in": "Увеличить",
"zoom_out": "Уменьшить",
"zoom_reset": "Сбросить масштаб",
"show_app": "Показать приложение",
"press_shortcut": "Нажмите сочетание клавиш",
"reset_defaults": "Сбросить настройки по умолчанию",
"reset_defaults_confirm": "Вы уверены, что хотите сбросить все горячие клавиши?",
"press_shortcut": "Нажмите сочетание клавиш",
"alt_warning": "Mac не поддерживает Option + буквы как горячие клавиши",
"reset_to_default": "Сбросить настройки по умолчанию",
"clear_shortcut": "Очистить сочетание клавиш",
"search_message": "Поиск сообщения",
"show_app": "Показать приложение",
"title": "Горячие клавиши",
"toggle_new_context": "Очистить контекст",
"toggle_show_assistants": "Переключить отображение ассистентов",
"toggle_show_topics": "Переключить отображение топиков",
"copy_last_message": "Копировать последнее сообщение",
"search_message": "Поиск сообщения",
"mini_window": "Быстрый помощник",
"clear_topic": "Очистить все сообщения",
"toggle_new_context": "Очистить контекст"
"zoom_in": "Увеличить",
"zoom_out": "Уменьшить",
"zoom_reset": "Сбросить масштаб"
},
"theme.auto": "Автоматически",
"theme.dark": "Темная",
@ -590,156 +740,37 @@
"topic.position.left": "Слева",
"topic.position.right": "Справа",
"topic.show.time": "Показывать время топика",
"tray.title": "Включить значок системного трея"
"tray.title": "Включить значок системного трея",
"input.target_language.russian": "Русский"
},
"translate": {
"any.language": "Любой язык",
"button.translate": "Перевести",
"close": "Закрыть",
"confirm": {
"content": "Перевод заменит исходный текст, продолжить?",
"title": "Перевод подтверждение"
},
"error.not_configured": "Модель перевода не настроена",
"error.failed": "Перевод не удалось",
"error.not_configured": "Модель перевода не настроена",
"input.placeholder": "Введите текст для перевода",
"output.placeholder": "Перевод",
"processing": "Перевод в процессе...",
"title": "Перевод",
"close": "Закрыть"
"title": "Перевод"
},
"tray": {
"quit": "Выйти",
"show_window": "Показать окно",
"show_mini_window": "Быстрый помощник"
"show_mini_window": "Быстрый помощник",
"show_window": "Показать окно"
},
"words": {
"knowledgeGraph": "Граф знаний",
"visualization": "Визуализация",
"quit": "Выйти",
"show_window": "Показать окно",
"quit": "Выйти"
"visualization": "Визуализация"
},
"knowledge": {
"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": "База знаний базы данных движок обновлен, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
"no_provider": "База знаний модель поставщика не настроена, база знаний больше не поддерживается, пожалуйста, создайте новую базу знаний",
"source": "Источник",
"chunk_size": "Размер фрагмента",
"chunk_overlap": "Перекрытие фрагмента",
"not_set": "Не установлено",
"settings": "Настройки базы знаний",
"document_count": "Количество запрошенных документов",
"document_count_help": "Количество запрошенных документов, вместе с ними передается больше информации, но и требуется больше токенов",
"document_count_default": "По умолчанию",
"chunk_size_placeholder": "По умолчанию (не рекомендуется изменять)",
"chunk_overlap_placeholder": "По умолчанию (не рекомендуется изменять)",
"chunk_size_tooltip": "Размер фрагмента, не превышающий модель контекста",
"chunk_overlap_tooltip": "Перекрытие фрагмента, не превышающее модель контекста",
"chunk_size_change_warning": "Размер фрагмента и перекрытие фрагмента могут быть изменены только для новых содержимого",
"chunk_size_too_large": "Размер фрагмента не может превышать модель контекста ({{max_context}})"
},
"models": {
"pinned": "Закреплено",
"search": "Поиск моделей...",
"stream_output": "Потоковый вывод",
"type": {
"select": "Выберите тип модели",
"text": "Текст",
"vision": "Изображение",
"embedding": "Встраиваемые",
"reasoning": "Рассуждение"
},
"all": "Все",
"vision": "Визуальные",
"websearch": "Веб-поисковые",
"free": "Бесплатные",
"reasoning": "Рассуждение",
"embedding": "Встраиваемые",
"embedding_model": "Встраиваемые модели",
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",
"dimensions": "{{dimensions}} мер",
"custom_parameters": "Пользовательские параметры",
"add_parameter": "Добавить параметр",
"parameter_name": "Имя параметра",
"parameter_type": {
"string": "Текст",
"number": "Число",
"boolean": "Логическое",
"json": "JSON"
}
},
"prompts": {
"title": "Вы - эксперт в общении, который суммирует разговоры пользователя в 10-символьном заголовке, совпадающем с языком пользователя, без использования знаков препинания и других специальных символов",
"explanation": "Объясните мне этот концепт",
"summarize": "Суммируйте этот текст"
},
"miniwindow": {
"feature": {
"chat": "Ответить на этот вопрос",
"translate": "Текст перевод",
"summary": "Содержание",
"explanation": "Объяснение"
},
"clipboard": {
"empty": "Буфер обмена пуст"
},
"input": {
"placeholder": {
"title": "Что вы хотите сделать с этим текстом?",
"empty": "Задайте вопрос {{model}}..."
}
},
"footer": {
"esc": "Нажмите ESC {{action}}",
"esc_close": "закрытия окна",
"esc_back": "возвращения",
"copy_last_message": "Нажмите C для копирования"
}
},
"auth": {
"oauth_button": "Авторизоваться с {{provider}}",
"get_key": "Получить",
"get_key_success": "Автоматический получение ключа API успешно",
"login": "Войти",
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную"
"docs": {
"title": "Документация"
}
}
}

View File

@ -2,12 +2,12 @@
"translation": {
"agents": {
"add.button": "添加到助手",
"add.knowledge_base": "知识库",
"add.knowledge_base.placeholder": "选择知识库",
"add.name": "名称",
"add.name.placeholder": "输入名称",
"add.prompt": "提示词",
"add.prompt.placeholder": "输入提示词",
"add.knowledge_base": "知识库",
"add.knowledge_base.placeholder": "选择知识库",
"add.title": "创建智能体",
"delete.popup.content": "确定要删除此智能体吗?",
"edit.message.add.title": "添加",
@ -42,20 +42,25 @@
"save.success": "保存成功",
"save.title": "保存到智能体",
"search": "搜索助手",
"settings.reasoning_effort": "思维链长度",
"settings.reasoning_effort.tip": "该设置仅支持推理模型",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.high": "长",
"settings.auto_reset_model": "自动重置模型",
"settings.auto_reset_model.tip": "创建新话题时自动重置模型",
"settings.default_model": "默认模型",
"settings.knowledge_base": "知识库设置",
"settings.model": "模型设置",
"settings.preset_messages": "预设消息",
"settings.prompt": "提示词设置",
"settings.knowledge_base": "知识库设置",
"settings.reasoning_effort": "思维链长度",
"settings.reasoning_effort.high": "长",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.tip": "该设置仅支持推理模型",
"title": "助手"
},
"auth": {
"error": "自动获取密钥失败,请手动获取",
"get_key": "获取",
"get_key_success": "自动获取密钥成功",
"login": "登录",
"oauth_button": "使用{{provider}}登录"
},
"button": {
"add": "添加",
"added": "已添加",
@ -69,6 +74,7 @@
"artifacts.button.download": "下载",
"artifacts.button.preview": "预览",
"assistant.search.placeholder": "搜索",
"deeply_thought": "已深度思考(用时 {{secounds}} 秒)",
"default.description": "你好,我是默认助手。你可以立刻开始跟我聊天。",
"default.name": "⭐️ 默认助手",
"default.topic.name": "默认话题",
@ -79,6 +85,7 @@
"input.context_count.tip": "上下文数",
"input.estimated_tokens.tip": "预估 token 数",
"input.expand": "展开",
"input.knowledge_base": "知识库",
"input.new.context": "清除上下文 {{Command}}",
"input.new_topic": "新话题 {{Command}}",
"input.pause": "暂停",
@ -90,19 +97,20 @@
"input.upload": "上传图片或文档",
"input.upload.document": "上传文档(模型不支持图片)",
"input.web_search": "开启网络搜索",
"input.knowledge_base": "知识库",
"input.file_not_supported": "模型不支持此文件类型",
"message.new.branch": "分支",
"message.new.branch.created": "新分支已创建",
"message.regenerate.model": "切换模型",
"message.new.context": "清除上下文",
"message.regenerate.model": "切换模型",
"message.useful": "有用",
"resend": "重新发送",
"save": "保存",
"settings.code_collapsible": "代码块可折叠",
"settings.context_count": "上下文数",
"settings.context_count.tip": "要保留在上下文中的消息数量,数值越大,上下文越长,消耗的 token 越多。普通聊天建议 5-10",
"settings.max": "不限",
"settings.max_tokens": "开启消息长度限制",
"settings.max_tokens.tip": "单次交互所用的最大 Token 数, 会影响返回结果的长度。普通聊天建议 500-800短文生成建议 800-2000代码生成建议 2000-3600长文生成建议切换模型到 4000 左右",
"settings.max_tokens.tip": "单次交互所用的最大 Token 数, 会影响返回结果的长度。要根据模型上下文限制来设置,否则会报错",
"settings.reset": "重置",
"settings.set_as_default": "应用到默认助手",
"settings.show_line_numbers": "代码显示行号",
@ -110,7 +118,10 @@
"settings.temperature.tip": "模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7",
"settings.top_p": "Top-P",
"settings.top_p.tip": "默认值为 1值越小AI 生成的内容越单调也越容易理解值越大AI 回复的词汇围越大,越多样化",
"settings.max_tokens.confirm": "开启消息长度限制",
"settings.max_tokens.confirm_content": "开启消息长度限制后,单次交互所用的最大 Token 数, 会影响返回结果的长度。要根据模型上下文限制来设置,否则会报错",
"suggestions.title": "建议的问题",
"thinking": "思考中",
"topics.auto_rename": "生成话题名",
"topics.clear.title": "清空消息",
"topics.edit.placeholder": "输入新名称",
@ -122,26 +133,27 @@
"topics.export.word": "导出为 Word",
"topics.list": "话题列表",
"topics.move_to": "移动到",
"topics.pinned": "固定话题",
"topics.title": "话题",
"topics.pinned":"固定话题",
"topics.unpinned":"取消固定",
"topics.unpinned": "取消固定",
"translate": "翻译",
"resend": "重新发送",
"thinking": "思考中",
"deeply_thought": "已深度思考(用时 {{secounds}} 秒)"
"topics.prompt": "话题提示词",
"topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词",
"topics.prompt.edit.title": "编辑话题提示词"
},
"common": {
"add": "添加",
"and": "和",
"assistant": "智能体",
"avatar": "头像",
"back": "返回",
"cancel": "取消",
"chat": "聊天",
"clear": "清除",
"close": "关闭",
"copy": "复制",
"cut": "剪切",
"default": "默认",
"knowledge_base": "知识库",
"delete": "删除",
"description": "描述",
"docs": "文档",
@ -149,6 +161,8 @@
"duplicate": "复制",
"edit": "编辑",
"footnote": "引用内容",
"footnotes": "引用内容",
"knowledge_base": "知识库",
"language": "语言",
"model": "模型",
"models": "模型",
@ -164,20 +178,11 @@
"select": "选择",
"topics": "话题",
"warning": "警告",
"you": "用户",
"clear": "清除",
"add": "添加",
"footnotes": "引用内容"
"you": "用户"
},
"error": {
"backup.file_format": "备份文件格式错误",
"chat.response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥",
"no_api_key": "API 密钥未配置",
"provider_disabled": "模型提供商未启用",
"render": {
"title": "渲染错误",
"description": "渲染公式失败,请检查公式格式是否正确"
},
"http": {
"400": "请求错误,请检查请求参数是否正确。如果修改了模型设置,请重置到默认设置",
"401": "身份验证失败,请检查 API 密钥是否正确",
@ -188,6 +193,13 @@
"502": "网关错误,请稍后再试",
"503": "服务不可用,请稍后再试",
"504": "网关超时,请稍后再试"
},
"model.exists": "模型已存在",
"no_api_key": "API 密钥未配置",
"provider_disabled": "模型提供商未启用",
"render": {
"description": "渲染公式失败,请检查公式格式是否正确",
"title": "渲染错误"
}
},
"export": {
@ -205,20 +217,20 @@
"all": "所有文件",
"count": "文件数",
"created_at": "创建时间",
"delete": "删除",
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除",
"delete.title": "删除文件",
"document": "文档",
"edit": "编辑",
"file": "文件",
"image": "图片",
"name": "文件名",
"open": "打开",
"size": "大小",
"type": "类型",
"text": "文本",
"title": "文件",
"edit": "编辑",
"delete": "删除",
"delete.title": "删除文件",
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除"
"type": "类型"
},
"history": {
"continue_chat": "继续聊天",
@ -228,6 +240,69 @@
"search.topics.empty": "没有找到相关话题, 点击回车键搜索所有消息",
"title": "话题搜索"
},
"knowledge": {
"add": {
"title": "添加知识库"
},
"add_directory": "添加目录",
"add_file": "添加文件",
"add_note": "添加笔记",
"add_sitemap": "站点地图",
"add_url": "添加网址",
"cancel_index": "取消索引",
"chunk_overlap": "重叠大小",
"chunk_overlap_placeholder": "默认值(不建议修改)",
"chunk_overlap_tooltip": "相邻文本块之间重复的内容量,确保分段后的文本块之间仍然有上下文联系,提升模型处理长文本的整体效果",
"chunk_size": "分段大小",
"chunk_size_change_warning": "分段大小和重叠大小修改只针对新添加的内容有效",
"chunk_size_placeholder": "默认值(不建议修改)",
"chunk_size_too_large": "分段大小不能超过模型上下文限制({{max_context}}",
"chunk_size_tooltip": "将文档切割分段,每段的大小,不能超过模型上下文限制",
"clear_selection": "清除选择",
"delete": "删除",
"delete_confirm": "确定要删除此知识库吗?",
"directories": "目录",
"directory_placeholder": "请输入目录路径",
"document_count": "请求文档分段数量",
"document_count_default": "默认",
"document_count_help": "请求文档分段数量越多,附带的信息越多,但需要消耗的 Token 也越多",
"drag_file": "拖拽文件到这里",
"empty": "暂无知识库",
"file_hint": "支持 {{file_types}} 格式",
"index_all": "索引全部",
"index_cancelled": "索引已取消",
"index_started": "索引开始",
"invalid_url": "无效的网址",
"model_info": "模型信息",
"no_bases": "暂无知识库",
"no_match": "未匹配到知识库内容",
"no_provider": "知识库模型服务商丢失,该知识库将不再支持,请重新创建知识库",
"not_set": "未设置",
"not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库",
"notes": "笔记",
"notes_placeholder": "输入此知识库的附加信息或上下文...",
"rename": "重命名",
"search": "搜索知识库",
"search_placeholder": "输入查询内容",
"settings": "知识库设置",
"sitemap_placeholder": "请输入站点地图 URL",
"sitemaps": "网站",
"source": "来源",
"status": "状态",
"status_completed": "已完成",
"status_failed": "失败",
"status_new": "已添加",
"status_pending": "等待中",
"status_processing": "处理中",
"threshold": "匹配度阈值",
"threshold_tooltip": "用于衡量用户问题与知识库内容之间的相关性0-1",
"threshold_placeholder": "未设置",
"threshold_too_large_or_small": "阈值不能大于1或小于0",
"title": "知识库",
"url_added": "网址已添加",
"url_placeholder": "请输入网址, 多个网址用回车分隔",
"urls": "网址"
},
"languages": {
"arabic": "阿拉伯文",
"chinese": "简体中文",
@ -257,61 +332,114 @@
"title": "Mermaid 图表"
},
"message": {
"api.check.model.title": "请选择要检测的模型",
"api.connection.failed": "连接失败",
"api.connection.success": "连接成功",
"api.check.model.title": "请选择要检测的模型",
"assistant.added.content": "智能体添加成功",
"backup.failed": "备份失败",
"backup.success": "备份成功",
"backup.start.success": "开始备份",
"backup.success": "备份成功",
"chat.completion.paused": "会话已停止",
"citations": "引用内容",
"copied": "已复制",
"copy.success": "复制成功",
"error.chunk_overlap_too_large": "分段重叠不能大于分段大小",
"error.enter.api.host": "请输入您的 API 地址",
"error.enter.api.key": "请输入您的 API 密钥",
"error.enter.model": "请选择一个模型",
"error.enter.name": "请输入知识库名称",
"error.chunk_overlap_too_large": "分段重叠不能大于分段大小",
"error.get_embedding_dimensions": "获取嵌入维度失败",
"error.invalid.api.host": "无效的 API 地址",
"error.invalid.api.key": "无效的 API 密钥",
"error.invalid.enter.model": "请选择一个模型",
"error.invalid.proxy.url": "无效的代理地址",
"error.invalid.webdav": "无效的 WebDAV 设置",
"error.invalid.api.key": "无效的 API 密钥",
"error.invalid.api.host": "无效的 API 地址",
"error.invalid.enter.model": "请选择一个模型",
"error.notion.export": "Notion 导入失败",
"error.notion.no_api_key": "未配置Notion ApiKey或Notion DatabaseID",
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
"group.delete.title": "删除分组消息",
"mention.title": "切换模型回答",
"message.code_style": "代码风格",
"message.delete.content": "确定要删除此消息吗?",
"message.delete.title": "删除消息",
"message.multi_model_style": "多模型回答样式",
"message.multi_model_style.fold": "折叠",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.style": "消息样式",
"message.style.bubble": "气泡",
"message.style.plain": "简洁",
"message.multi_model_style": "多模型回答样式",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.fold": "折叠",
"regenerate.confirm": "重新生成会覆盖当前消息",
"reset.confirm.content": "确定要重置所有数据吗?",
"reset.double.confirm.content": "你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?",
"reset.double.confirm.title": "数据丢失!!!",
"restore.success": "恢复成功",
"save.success.title": "保存成功",
"success.notion.export": "导入Notion成功",
"switch.disabled": "请等待当前回复完成后操作",
"topic.added": "话题添加成功",
"upgrade.success.button": "重启",
"upgrade.success.content": "重启用以完成升级",
"upgrade.success.title": "升级成功",
"regenerate.confirm": "重新生成会覆盖当前消息",
"copy.success": "复制成功",
"error.get_embedding_dimensions": "获取嵌入维度失败",
"group.delete.title": "删除分组消息",
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
"mention.title": "切换模型回答",
"error.notion.export":"Notion 导入失败",
"error.notion.no_api_key":"未配置Notion ApiKey或Notion DatabaseID",
"success.notion.export":"导入Notion成功",
"warn.notion.exporting":"Notion正在导入请勿重复导入",
"citations": "引用内容"
"warn.notion.exporting": "Notion正在导入请勿重复导入"
},
"minapp": {
"title": "小程序",
"sidebar.add.title": "添加到侧边栏",
"sidebar.remove.title": "从侧边栏移除"
"sidebar.remove.title": "从侧边栏移除",
"title": "小程序"
},
"miniwindow": {
"clipboard": {
"empty": "剪贴板为空"
},
"feature": {
"chat": "回答此问题",
"explanation": "解释说明",
"summary": "内容总结",
"translate": "文本翻译"
},
"footer": {
"copy_last_message": "按 C 键复制",
"esc": "按 ESC {{action}}",
"esc_back": "返回",
"esc_close": "关闭窗口"
},
"input": {
"placeholder": {
"empty": "询问 {{model}} 获取帮助...",
"title": "你想对下方文字做什么"
}
}
},
"models": {
"add_parameter": "添加参数",
"all": "全部",
"custom_parameters": "自定义参数",
"dimensions": "{{dimensions}} 维",
"embedding": "嵌入",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"free": "免费",
"parameter_name": "参数名称",
"parameter_type": {
"boolean": "布尔值",
"json": "JSON",
"number": "数字",
"string": "文本"
},
"pinned": "已固定",
"reasoning": "推理",
"search": "搜索模型...",
"stream_output": "流式输出",
"type": {
"embedding": "嵌入",
"reasoning": "推理",
"select": "选择模型类型",
"text": "文本",
"vision": "图像"
},
"vision": "视觉",
"websearch": "联网"
},
"ollama": {
"keep_alive_time.description": "对话后模型在内存中保持的时间默认5分钟",
@ -319,6 +447,12 @@
"keep_alive_time.title": "保持活跃时间",
"title": "Ollama"
},
"lmstudio": {
"keep_alive_time.description": "对话后模型在内存中保持的时间默认5分钟",
"keep_alive_time.placeholder": "分钟",
"keep_alive_time.title": "保持活跃时间",
"title": "LM Studio"
},
"paintings": {
"button.delete.image": "删除图片",
"button.delete.image.confirm": "确定要删除此图片吗?",
@ -332,25 +466,35 @@
"negative_prompt_tip": "描述你不想在图片中出现的内容",
"number_images": "生成数量",
"number_images_tip": "一次生成的图片数量 (1-4)",
"prompt_enhancement": "提示词增强",
"prompt_enhancement_tip": "开启后将提示重写为详细的、适合模型的版本",
"prompt_placeholder": "描述你想创建的图片,例如:一个宁静的湖泊,夕阳西下,远处是群山",
"regenerate.confirm": "这将覆盖已生成的图片,是否继续?",
"seed": "随机种子",
"seed_tip": "相同的种子和提示词可以生成相似的图片",
"title": "图片",
"prompt_enhancement": "提示词增强",
"prompt_enhancement_tip": "开启后将提示重写为详细的、适合模型的版本"
"title": "图片"
},
"prompts": {
"explanation": "帮我解释一下这个概念",
"summarize": "帮我总结一下这段话",
"title": "你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号"
},
"provider": {
"infini": "无问芯穹",
"perplexity": "Perplexity",
"dmxapi": "DMXAPI",
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baidu-cloud": "百度云千帆",
"baichuan": "百川",
"baidu-cloud": "百度云千帆",
"dashscope": "阿里云百炼",
"modelscope": "ModelScope 魔搭",
"deepseek": "深度求索",
"doubao": "豆包",
"doubao": "火山引擎",
"fireworks": "Fireworks",
"gemini": "Gemini",
"gitee-ai": "Gitee AI",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
@ -364,21 +508,23 @@
"nvidia": "英伟达",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"lmstudio": "LM Studio",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"ppio": "PPIO 派欧云",
"qwenlm": "QwenLM",
"silicon": "硅基流动",
"stepfun": "阶跃星辰",
"together": "Together",
"yi": "零一万物",
"zhinao": "360智脑",
"zhipu": "智谱AI",
"qwenlm": "QwenLM"
"zhipu": "智谱AI"
},
"settings": {
"about": "关于我们",
"about.checkingUpdate": "正在检查更新...",
"about.checkUpdate": "检查更新",
"about.checkUpdate.available": "立即更新",
"about.checkingUpdate": "正在检查更新...",
"about.contact.button": "邮件",
"about.contact.title": "邮件联系",
"about.description": "一款为创造者而生的 AI 助手",
@ -389,13 +535,13 @@
"about.license.title": "许可证",
"about.releases.button": "查看",
"about.releases.title": "更新日志",
"about.social.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": "默认助手",
@ -412,39 +558,61 @@
"title": "清除缓存"
},
"data.title": "数据目录",
"notion.api_key": "Notion 密钥",
"notion.database_id": "Notion 数据库ID",
"notion.title": "Notion 配置",
"notion.check": {
"button": "检查",
"fail": "连接失败,请检查配置",
"success": "连接成功",
"error": "连接异常,请检查网络",
"empty_api_key": "未配置Api_key",
"empty_database_id": "未配置Database_id"
},
"title": "数据设置",
"webdav.autoSync": "自动备份",
"webdav.autoSync.off": "关闭",
"webdav.backup.button": "备份到 WebDAV",
"webdav.host": "WebDAV 地址",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.hours": "小时",
"webdav.lastSync": "上次备份时间",
"webdav.minutes": "分钟",
"webdav.noSync": "等待下次备份",
"webdav.password": "WebDAV 密码",
"webdav.path": "WebDAV 路径",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "自动备份",
"webdav.minutes": "分钟",
"webdav.hours": "小时",
"webdav.restore.button": "从 WebDAV 恢复",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV 用户名",
"webdav.syncStatus": "备份状态",
"webdav.autoSync.off": "关闭",
"webdav.noSync": "等待下次备份",
"webdav.restore.content": "从 WebDAV 恢复将覆盖当前数据,是否继续?",
"webdav.restore.title": "从 WebDAV 恢复",
"webdav.syncError": "备份错误",
"webdav.lastSync": "上次备份时间",
"notion.api_key": "Notion 密钥",
"notion.database_id": "Notion 数据库ID",
"notion.title": "Notion 配置"
},
"quickAssistant": {
"title": "快捷助手",
"click_tray_to_show": "点击托盘图标启动",
"enable_quick_assistant": "启用快捷助手",
"use_shortcut_to_show": "右键点击托盘图标或使用快捷键启动"
"webdav.syncStatus": "备份状态",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV 用户名"
},
"display.custom.css": "自定义 CSS",
"display.custom.css.placeholder": "/* 这里写自定义CSS */",
"display.minApp.disabled": "隐藏的小程序",
"display.minApp.empty": "把要隐藏的小程序从左侧拖拽到这里",
"display.minApp.title": "小程序显示设置",
"display.minApp.visible": "显示的小程序",
"display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏",
"display.sidebar.disabled": "隐藏的图标",
"display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里",
"display.sidebar.files.icon": "显示文件图标",
"display.sidebar.knowledge.icon": "显示知识图标",
"display.sidebar.minapp.icon": "显示小程序图标",
"display.sidebar.painting.icon": "显示绘画图标",
"display.sidebar.title": "侧边栏设置",
"display.sidebar.translate.icon": "显示翻译图标",
"display.sidebar.visible": "显示的图标",
"display.title": "显示设置",
"display.topic.title": "话题设置",
"font_size.title": "消息字体大小",
"general": "常规设置",
"general.backup.button": "备份",
"general.backup.title": "数据备份与恢复",
"general.display.title": "显示设置",
"general.manually_check_update.title": "关闭更新检测",
"general.reset.button": "重置",
"general.reset.title": "重置数据",
@ -453,24 +621,6 @@
"general.user_name": "用户名",
"general.user_name.placeholder": "请输入用户名",
"general.view_webdav_settings": "查看 WebDAV 设置",
"general.display.title": "显示设置",
"display.sidebar.translate.icon": "显示翻译图标",
"display.sidebar.painting.icon": "显示绘画图标",
"display.sidebar.minapp.icon": "显示小程序图标",
"display.sidebar.knowledge.icon": "显示知识图标",
"display.sidebar.files.icon": "显示文件图标",
"display.sidebar.title": "侧边栏设置",
"display.sidebar.visible": "显示的图标",
"display.sidebar.disabled": "隐藏的图标",
"display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏",
"display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里",
"display.minApp.title": "小程序显示设置",
"display.minApp.visible": "显示的小程序",
"display.minApp.disabled": "隐藏的小程序",
"display.minApp.empty": "把要隐藏的小程序从左侧拖拽到这里",
"display.topic.title": "话题设置",
"display.custom.css": "自定义 CSS",
"display.custom.css.placeholder": "/* 这里写自定义CSS */",
"input.auto_translate_with_space": "快速敲击3次空格翻译",
"input.target_language": "目标语言",
"input.target_language.chinese": "简体中文",
@ -480,16 +630,16 @@
"input.target_language.russian": "俄文",
"messages.divider": "消息分割线",
"messages.input.paste_long_text_as_file": "长文本粘贴为文件",
"messages.input.paste_long_text_threshold": "长文本长度",
"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.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
"messages.model.title": "模型设置",
"messages.title": "消息设置",
"messages.use_serif_font": "使用衬线字体",
"messages.input.paste_long_text_threshold": "长文本长度",
"model": "默认模型",
"models.add.add_model": "添加模型",
"models.add.group_name": "分组名称",
@ -503,15 +653,15 @@
"models.default_assistant_model": "默认助手模型",
"models.default_assistant_model_description": "创建新助手时使用的模型,如果助手未设置模型,则使用此模型",
"models.empty": "没有模型",
"models.enable_topic_naming": "话题自动重命名",
"models.topic_naming_model": "话题命名模型",
"models.topic_naming_model_description": "自动命名新话题时使用的模型",
"models.topic_naming_model_setting_title": "话题命名模型设置",
"models.topic_naming_prompt": "话题命名提示词",
"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",
@ -519,11 +669,12 @@
"add.type": "提供商类型",
"api.url.preview": "预览: {{url}}",
"api.url.reset": "重置",
"api.url.tip": "/结尾忽略v1版本#结尾制使用输入地址",
"api.url.tip": "/结尾忽略v1版本#结尾制使用输入地址",
"api_host": "API 地址",
"api_key": "API 密钥",
"api_key.tip": "多个密钥使用逗号分隔",
"api_version": "API 版本",
"charge": "充值",
"check": "检查",
"check_all_keys": "检查所有密钥",
"check_multiple_keys": "检查多个 API 密钥",
@ -532,7 +683,6 @@
"docs_check": "查看",
"docs_more_details": "获取更多详情",
"get_api_key": "点击这里获取密钥",
"charge": "充值",
"no_models": "请先添加模型再检查 API 连接",
"not_checked": "未检查",
"remove_duplicate_keys": "移除重复密钥",
@ -550,28 +700,34 @@
"title": "代理设置"
},
"proxy.title": "代理地址",
"quickAssistant": {
"click_tray_to_show": "点击托盘图标启动",
"enable_quick_assistant": "启用快捷助手",
"title": "快捷助手",
"use_shortcut_to_show": "右键点击托盘图标或使用快捷键启动"
},
"shortcuts": {
"action": "操作",
"alt_warning": "Mac 系统不能使用 Option + 字母作为快捷键",
"clear_shortcut": "清除快捷键",
"clear_topic": "清空消息",
"copy_last_message": "复制上一条消息",
"key": "按键",
"mini_window": "快捷助手",
"new_topic": "新建话题",
"title": "快捷方式",
"zoom_in": "放大界面",
"zoom_out": "缩小界面",
"zoom_reset": "重置缩放",
"show_app": "显示应用",
"press_shortcut": "按下快捷键",
"reset_defaults": "重置默认快捷键",
"reset_defaults_confirm": "确定要重置所有快捷键吗?",
"press_shortcut": "按下快捷键",
"alt_warning": "Mac 系统不能使用 Option + 字母作为快捷键",
"reset_to_default": "重置为默认",
"clear_shortcut": "清除快捷键",
"search_message": "搜索消息",
"show_app": "显示应用",
"title": "快捷方式",
"toggle_new_context": "清除上下文",
"toggle_show_assistants": "切换助手显示",
"toggle_show_topics": "切换话题显示",
"copy_last_message": "复制上一条消息",
"search_message": "搜索消息",
"mini_window": "快捷助手",
"clear_topic": "清空消息",
"toggle_new_context": "清除上下文"
"zoom_in": "放大界面",
"zoom_out": "缩小界面",
"zoom_reset": "重置缩放"
},
"theme.auto": "跟随系统",
"theme.dark": "深色主题",
@ -590,151 +746,31 @@
"translate": {
"any.language": "任意语言",
"button.translate": "翻译",
"close": "关闭",
"confirm": {
"content": "翻译后将覆盖原文,是否继续?",
"title": "翻译确认"
},
"error.not_configured": "翻译模型未配置",
"error.failed": "翻译失败",
"error.not_configured": "翻译模型未配置",
"input.placeholder": "输入文本进行翻译",
"output.placeholder": "翻译",
"processing": "翻译中...",
"title": "翻译",
"close": "关闭"
"title": "翻译"
},
"tray": {
"quit": "退出",
"show_window": "显示窗口",
"show_mini_window": "快捷助手"
"show_mini_window": "快捷助手",
"show_window": "显示窗口"
},
"words": {
"knowledgeGraph": "知识图谱",
"visualization": "可视化",
"quit": "退出",
"show_window": "显示窗口",
"quit": "退出"
"visualization": "可视化"
},
"knowledge": {
"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": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库",
"no_provider": "知识库模型服务商丢失,该知识库将不再支持,请重新创建知识库",
"source": "来源",
"chunk_size": "分段大小",
"chunk_overlap": "重叠大小",
"not_set": "未设置",
"settings": "知识库设置",
"document_count": "请求文档数量",
"document_count_help": "请求文档数量越多,附带的信息越多,但需要消耗的 Token 也越多",
"document_count_default": "默认",
"chunk_size_placeholder": "默认值(不建议修改)",
"chunk_overlap_placeholder": "默认值(不建议修改)",
"chunk_size_tooltip": "将文档切割分段,每段的大小,不能超过模型上下文限制",
"chunk_overlap_tooltip": "相邻文本块之间重复的内容量,确保分段后的文本块之间仍然有上下文联系,提升模型处理长文本的整体效果",
"chunk_size_change_warning": "分段大小和重叠大小修改只针对新添加的内容有效",
"chunk_size_too_large": "分段大小不能超过模型上下文限制({{max_context}}"
},
"models": {
"pinned": "已固定",
"search": "搜索模型...",
"stream_output": "流式输出",
"type": {
"select": "选择模型类型",
"text": "文本",
"vision": "图像",
"embedding": "嵌入",
"reasoning": "推理"
},
"all": "全部",
"vision": "视觉",
"websearch": "联网",
"free": "免费",
"reasoning": "推理",
"embedding": "嵌入",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"dimensions": "{{dimensions}} 维",
"custom_parameters": "自定义参数",
"add_parameter": "添加参数",
"parameter_name": "参数名称",
"parameter_type": {
"string": "文本",
"number": "数字",
"boolean": "布尔值",
"json": "JSON"
}
},
"prompts": {
"title": "你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号",
"explanation": "帮我解释一下这个概念",
"summarize": "帮我总结一下这段话"
},
"miniwindow": {
"feature": {
"chat": "回答此问题",
"translate": "文本翻译",
"summary": "内容总结",
"explanation": "解释说明"
},
"clipboard": {
"empty": "剪贴板为空"
},
"input": {
"placeholder": {
"title": "你想对下方文字做什么",
"empty": "询问 {{model}} 获取帮助..."
}
},
"footer": {
"esc": "按 ESC {{action}}",
"esc_close": "关闭窗口",
"esc_back": "返回",
"copy_last_message": "按 C 键复制"
}
},
"auth": {
"oauth_button": "使用{{provider}}登录",
"get_key": "获取",
"get_key_success": "自动获取密钥成功",
"login": "登录",
"error": "自动获取密钥失败,请手动获取"
"docs": {
"title": "帮助文档"
}
}
}

View File

@ -2,12 +2,12 @@
"translation": {
"agents": {
"add.button": "添加到助手",
"add.knowledge_base": "知識庫",
"add.knowledge_base.placeholder": "選擇知識庫",
"add.name": "名稱",
"add.name.placeholder": "輸入名稱",
"add.prompt": "提示詞",
"add.prompt.placeholder": "輸入提示詞",
"add.knowledge_base": "知識庫",
"add.knowledge_base.placeholder": "選擇知識庫",
"add.title": "创建智能體",
"delete.popup.content": "確定要刪除此智能體嗎?",
"edit.message.add.title": "添加",
@ -42,20 +42,25 @@
"save.success": "儲存成功",
"save.title": "儲存到智能體",
"search": "搜尋助手...",
"settings.reasoning_effort": "思維鏈長度",
"settings.reasoning_effort.tip": "該設置僅支持推理模型",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.high": "長",
"settings.auto_reset_model": "自動重置模型",
"settings.auto_reset_model.tip": "每次新的話題時自動重置模型",
"settings.default_model": "預設模型",
"settings.knowledge_base": "知識庫設定",
"settings.model": "模型設定",
"settings.preset_messages": "預設訊息",
"settings.prompt": "提示詞設定",
"settings.knowledge_base": "知識庫設定",
"settings.reasoning_effort": "思維鏈長度",
"settings.reasoning_effort.high": "長",
"settings.reasoning_effort.low": "短",
"settings.reasoning_effort.medium": "中",
"settings.reasoning_effort.tip": "該設置僅支持推理模型",
"title": "助手"
},
"auth": {
"error": "自動獲取密鑰失敗,請手動獲取",
"get_key": "獲取",
"get_key_success": "自動獲取密鑰成功",
"login": "登入",
"oauth_button": "使用{{provider}}登入"
},
"button": {
"add": "添加",
"added": "已添加",
@ -69,6 +74,7 @@
"artifacts.button.download": "下載",
"artifacts.button.preview": "預覽",
"assistant.search.placeholder": "搜尋",
"deeply_thought": "已深度思考(用時 {{secounds}} 秒)",
"default.description": "你好,我是預設助手。你可以立即開始與我聊天。",
"default.name": "⭐️ 預設助手",
"default.topic.name": "預設話題",
@ -79,6 +85,7 @@
"input.context_count.tip": "上下文數量",
"input.estimated_tokens.tip": "預估 Token 數",
"input.expand": "展開",
"input.knowledge_base": "知識庫",
"input.new.context": "清除上下文 {{Command}}",
"input.new_topic": "新話題 {{Command}}",
"input.pause": "暫停",
@ -90,19 +97,20 @@
"input.upload": "上傳圖片或文檔",
"input.upload.document": "上傳文檔(模型不支持圖片)",
"input.web_search": "開啟網路搜索",
"input.knowledge_base": "知識庫",
"input.file_not_supported": "模型不支持此文件類型",
"message.new.branch": "分支",
"message.new.branch.created": "新分支已建立",
"message.regenerate.model": "切換模型",
"message.new.context": "新上下文",
"message.regenerate.model": "切換模型",
"message.useful": "有用",
"resend": "重新發送",
"save": "保存",
"settings.code_collapsible": "代码块可折叠",
"settings.context_count": "上下文",
"settings.context_count.tip": "在上下文中保留的前幾則訊息。",
"settings.max": "最大",
"settings.max_tokens": "啟用最大 Token 限制",
"settings.max_tokens.tip": "模型可以生成的最大 Token 數。普通聊天建議 500-800。短文生成建議 800-2000。代碼生成建議 2000-3600。長文生成建議超過 4000。",
"settings.max_tokens.tip": "模型可以生成的最大 Token 數。要根据模型上下文限制来设置,否则会报错",
"settings.reset": "重置",
"settings.set_as_default": "設為預設助手",
"settings.show_line_numbers": "代码顯示行號",
@ -110,7 +118,10 @@
"settings.temperature.tip": "模型產生文字的隨機程度。數值越高,回應內容越具多樣性、創意性及隨機性;設定為 0 則會依據事實回答。一般聊天建議設定為 0.7",
"settings.top_p": "Top-P",
"settings.top_p.tip": "模型生成文本的隨機程度。值越小AI 生成的內容越單調也越容易理解值越大AI 回覆的詞彙範圍越大,越多樣化",
"settings.max_tokens.confirm": "啟用消息長度限制",
"settings.max_tokens.confirm_content": "啟用消息長度限制後,單次交互所用的最大 Token 數, 會影響返回結果的長度。要根據模型上下文限制來設置,否則會報錯",
"suggestions.title": "建議的問題",
"thinking": "思考中",
"topics.auto_rename": "自動重新命名",
"topics.clear.title": "清空消息",
"topics.edit.placeholder": "輸入新名稱",
@ -122,32 +133,35 @@
"topics.export.word": "導出為 Word",
"topics.list": "話題列表",
"topics.move_to": "移動到",
"topics.title": "話題",
"topics.pinned": "固定話題",
"topics.title": "話題",
"topics.unpinned": "取消固定",
"translate": "翻譯",
"resend": "重新發送",
"thinking": "思考中",
"deeply_thought": "已深度思考(用時 {{secounds}} 秒)"
"topics.prompt": "話題提示詞",
"topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞",
"topics.prompt.edit.title": "編輯話題提示詞"
},
"common": {
"add": "添加",
"and": "與",
"assistant": "智能體",
"avatar": "頭像",
"back": "返回",
"cancel": "取消",
"chat": "聊天",
"clear": "清除",
"close": "關閉",
"copy": "複製",
"cut": "剪下",
"default": "預設",
"knowledge_base": "知識庫",
"delete": "刪除",
"description": "描述",
"docs": "文件",
"download": "下載",
"duplicate": "複製",
"edit": "編輯",
"footnotes": "引用",
"knowledge_base": "知識庫",
"language": "語言",
"model": "模型",
"models": "模型",
@ -164,19 +178,11 @@
"topics": "話題",
"warning": "警告",
"you": "您",
"clear": "清除",
"add": "添加",
"footnotes": "引用"
"footnote": "引用內容"
},
"error": {
"backup.file_format": "備份文件格式錯誤",
"chat.response": "出現錯誤。如果尚未配置 API 密鑰,請前往設定 > 模型提供者中配置密鑰",
"no_api_key": "API 密鑰未配置",
"provider_disabled": "模型提供商未啟用",
"render": {
"title": "渲染錯誤",
"description": "渲染公式失敗,請檢查公式格式是否正確"
},
"http": {
"400": "請求錯誤,請檢查請求參數是否正確。如果修改了模型設置,請重置到預設設置",
"401": "身份驗證失敗,請檢查 API 密鑰是否正確",
@ -187,6 +193,13 @@
"502": "網關錯誤,請稍後再試",
"503": "服務不可用,請稍後再試",
"504": "網關超時,請稍後再試"
},
"model.exists": "模型已存在",
"no_api_key": "API 密鑰未配置",
"provider_disabled": "模型提供商未啟用",
"render": {
"description": "渲染公式失敗,請檢查公式格式是否正確",
"title": "渲染錯誤"
}
},
"export": {
@ -204,20 +217,20 @@
"all": "所有檔案",
"count": "數量",
"created_at": "建立時間",
"delete": "刪除",
"delete.content": "刪除檔案會刪除檔案在所有消息中的引用,確定要刪除此檔案嗎?",
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除",
"delete.title": "刪除檔案",
"document": "文檔",
"edit": "編輯",
"file": "檔案",
"image": "圖片",
"name": "名稱",
"open": "打開",
"size": "大小",
"type": "類型",
"text": "文本",
"title": "檔案",
"edit": "編輯",
"delete": "刪除",
"delete.title": "刪除檔案",
"delete.content": "刪除檔案會刪除檔案在所有消息中的引用,確定要刪除此檔案嗎?",
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除"
"type": "類型"
},
"history": {
"continue_chat": "繼續聊天",
@ -227,6 +240,69 @@
"search.topics.empty": "沒有找到相關話題, 點擊回車鍵搜尋所有訊息",
"title": "搜尋話題"
},
"knowledge": {
"add": {
"title": "添加知識庫"
},
"add_directory": "添加目錄",
"add_file": "添加文件",
"add_note": "添加筆記",
"add_sitemap": "網站地圖",
"add_url": "添加網址",
"cancel_index": "取消索引",
"chunk_overlap": "重疊大小",
"chunk_overlap_placeholder": "預設值(不建議修改)",
"chunk_overlap_tooltip": "相鄰文本塊之間重複的內容量,確保分段後的文本塊之間仍然有上下文聯繫,提升模型處理長文本的整體效果",
"chunk_size": "分段大小",
"chunk_size_change_warning": "分段大小和重疊大小修改只針對新添加的內容有效",
"chunk_size_placeholder": "預設值(不建議修改)",
"chunk_size_too_large": "分段大小不能超過模型上下文限制({{max_context}}",
"chunk_size_tooltip": "將文件切割分段,每段的大小,不能超過模型上下文限制",
"clear_selection": "清除選擇",
"delete": "刪除",
"delete_confirm": "確定要刪除此知識庫嗎?",
"directories": "目錄",
"directory_placeholder": "請輸入目錄路徑",
"document_count": "請求文件分段數量",
"document_count_default": "預設",
"document_count_help": "請求文件分段數量越多,附帶的資訊越多,但需要消耗的 Token 也越多",
"drag_file": "拖拽文件到這裡",
"empty": "暫無知識庫",
"file_hint": "支持 {{file_types}} 格式",
"index_all": "索引全部",
"index_cancelled": "索引已取消",
"index_started": "索引開始",
"invalid_url": "無效的網址",
"model_info": "模型信息",
"no_bases": "暫無知識庫",
"no_provider": "知識庫模型提供商遺失,該知識庫將不再支持,請重新創建知識庫",
"not_set": "未設置",
"not_support": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫",
"notes": "筆記",
"notes_placeholder": "輸入此知識庫的附加資訊或上下文...",
"rename": "重命名",
"search": "搜尋知識庫",
"search_placeholder": "輸入查詢內容",
"settings": "知識庫設定",
"sitemap_placeholder": "請輸入網站地圖 URL",
"sitemaps": "網站",
"source": "來源",
"status": "狀態",
"status_completed": "已完成",
"status_failed": "失敗",
"status_new": "已添加",
"status_pending": "等待中",
"status_processing": "處理中",
"title": "知識庫",
"url_added": "網址已添加",
"url_placeholder": "請輸入網址, 多個網址用回車分隔",
"urls": "網址",
"threshold_tooltip": "用於衡量用戶問題與知識庫內容之間的相關性0-1",
"threshold_placeholder": "未設置",
"threshold_too_large_or_small": "閾值不能大於1或小於0",
"no_match": "未匹配到知識庫內容",
"threshold": "匹配度閾值"
},
"languages": {
"arabic": "阿拉伯文",
"chinese": "簡體中文",
@ -256,61 +332,114 @@
"title": "Mermaid 圖表"
},
"message": {
"api.check.model.title": "請選擇要檢測的模型",
"api.connection.failed": "連接失敗",
"api.connection.success": "連接成功",
"api.check.model.title": "請選擇要檢測的模型",
"assistant.added.content": "智能體添加成功",
"backup.failed": "備份失敗",
"backup.success": "備份成功",
"backup.start.success": "開始備份",
"backup.success": "備份成功",
"chat.completion.paused": "聊天完成已暫停",
"citations": "參考文獻",
"copied": "已複製",
"copy.success": "複製成功",
"error.chunk_overlap_too_large": "分段重疊不能大於分段大小",
"error.enter.api.host": "請先輸入您的 API 主機地址",
"error.enter.api.key": "請先輸入您的 API 密鑰",
"error.enter.model": "請先選擇一個模型",
"error.enter.name": "請先輸入知識庫名稱",
"error.chunk_overlap_too_large": "分段重疊不能大於分段大小",
"error.get_embedding_dimensions": "獲取嵌入維度失敗",
"error.invalid.enter.model": "請選擇一個模型",
"error.invalid.proxy.url": "無效的代理 URL",
"error.invalid.webdav": "無效的 WebDAV 設定",
"error.invalid.enter.api.host": "請輸入有效的 API 主機地址",
"error.invalid.enter.api.key": "請輸入有效的 API 密鑰",
"error.invalid.enter.model": "請選擇一個模型",
"error.notion.export": "Notion 匯入失敗",
"error.notion.no_api_key": "未配置 Notion ApiKey 或 Notion DatabaseID",
"group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答",
"group.delete.title": "刪除分組消息",
"mention.title": "切換模型回答",
"message.code_style": "程式碼風格",
"message.delete.content": "確定要刪除此訊息嗎?",
"message.delete.title": "刪除訊息",
"message.multi_model_style": "多模型回答樣式",
"message.multi_model_style.fold": "折疊",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.style": "消息樣式",
"message.style.bubble": "氣泡",
"message.style.plain": "簡潔",
"message.multi_model_style": "多模型回答樣式",
"message.multi_model_style.horizontal": "水平",
"message.multi_model_style.vertical": "垂直",
"message.multi_model_style.fold": "折疊",
"regenerate.confirm": "重新生成會覆蓋當前訊息",
"reset.confirm.content": "確定要清除所有資料嗎?",
"reset.double.confirm.content": "所有資料將會被清除,您確定要繼續嗎?",
"reset.double.confirm.title": "資料將會丟失!!!",
"restore.success": "恢復成功",
"save.success.title": "保存成功",
"success.notion.export": "匯入 Notion 成功",
"switch.disabled": "請等待當前回覆完成",
"topic.added": "新話題已添加",
"upgrade.success.button": "重新啟動",
"upgrade.success.content": "請重新啟動應用以完成升級",
"upgrade.success.title": "升級成功",
"regenerate.confirm": "重新生成會覆蓋當前訊息",
"copy.success": "複製成功",
"error.get_embedding_dimensions": "獲取嵌入維度失敗",
"group.delete.title": "刪除分組消息",
"group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答",
"mention.title": "切換模型回答",
"error.notion.export": "Notion 匯入失敗",
"error.notion.no_api_key": "未配置 Notion ApiKey 或 Notion DatabaseID",
"success.notion.export": "匯入 Notion 成功",
"warn.notion.exporting": "Notion 正在匯入,請勿重複匯入",
"citations": "參考文獻"
"error.invalid.api.host": "無效的 API 位址",
"error.invalid.api.key": "無效的 API 密鑰"
},
"minapp": {
"title": "小程序",
"sidebar.add.title": "添加到側邊欄",
"sidebar.remove.title": "從側邊欄移除"
"sidebar.remove.title": "從側邊欄移除",
"title": "小程序"
},
"miniwindow": {
"clipboard": {
"empty": "剪貼板為空"
},
"feature": {
"chat": "回答此問題",
"explanation": "解釋說明",
"summary": "內容總結",
"translate": "文本翻譯"
},
"footer": {
"copy_last_message": "按 C 鍵複製",
"esc": "按 ESC {{action}}",
"esc_back": "返回",
"esc_close": "關閉窗口"
},
"input": {
"placeholder": {
"empty": "詢問 {{model}} 獲取幫助...",
"title": "你想對下方文字做什麼"
}
}
},
"models": {
"add_parameter": "添加參數",
"all": "全部",
"custom_parameters": "自定義參數",
"dimensions": "{{dimensions}} 維",
"embedding": "嵌入",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"free": "免費",
"parameter_name": "參數名稱",
"parameter_type": {
"boolean": "布林值",
"json": "JSON",
"number": "數字",
"string": "文字"
},
"pinned": "已固定",
"reasoning": "推理",
"search": "搜尋模型...",
"stream_output": "串流輸出",
"type": {
"embedding": "嵌入",
"reasoning": "推理",
"select": "選擇模型類型",
"text": "文字",
"vision": "圖像"
},
"vision": "視覺",
"websearch": "網路搜索"
},
"ollama": {
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。",
@ -318,6 +447,12 @@
"keep_alive_time.title": "保持活躍時間",
"title": "Ollama"
},
"lmstudio": {
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。",
"keep_alive_time.placeholder": "分鐘",
"keep_alive_time.title": "保持活躍時間",
"title": "LM Studio"
},
"paintings": {
"button.delete.image": "刪除繪圖",
"button.delete.image.confirm": "確定要刪除此繪圖嗎?",
@ -331,25 +466,32 @@
"negative_prompt_tip": "描述你不想在圖片中出現的內容",
"number_images": "生成數量",
"number_images_tip": "一次生成的圖片數量 (1-4)",
"prompt_enhancement": "提示詞增強",
"prompt_enhancement_tip": "開啟後將提示重寫為詳細的、適合模型的版本",
"prompt_placeholder": "描述你想創建的圖片,例如:一個寧靜的湖泊,夕陽西下,遠處是群山",
"regenerate.confirm": "這將覆蓋已生成的圖片,是否繼續?",
"seed": "隨機種子",
"seed_tip": "相同的種子和提示詞可以生成相似的圖片",
"title": "繪圖",
"prompt_enhancement": "提示詞增強",
"prompt_enhancement_tip": "開啟後將提示重寫為詳細的、適合模型的版本"
"title": "繪圖"
},
"prompts": {
"explanation": "幫我解釋一下這個概念",
"summarize": "幫我總結一下這段話",
"title": "你是一名擅長會話的助理,你需要將用戶的會話總結為 10 個字以內的標題,標題語言與用戶的首要語言一致,不要使用標點符號和其他特殊符號"
},
"provider": {
"aihubmix": "AiHubMix",
"anthropic": "Anthropic",
"azure-openai": "Azure OpenAI",
"baidu-cloud": "百度云千帆",
"baichuan": "百川",
"baidu-cloud": "百度云千帆",
"dashscope": "阿里雲百鍊",
"modelscope": "ModelScope 魔搭",
"deepseek": "深度求索",
"doubao": "豆包",
"doubao": "火山引擎",
"fireworks": "Fireworks",
"gemini": "Gemini",
"gitee-ai": "Gitee AI",
"github": "GitHub Models",
"graphrag-kylin-mountain": "GraphRAG",
"grok": "Grok",
@ -363,21 +505,26 @@
"nvidia": "輝達",
"ocoolai": "ocoolAI",
"ollama": "Ollama",
"lmstudio": "LM Studio",
"openai": "OpenAI",
"openrouter": "OpenRouter",
"ppio": "PPIO 派歐雲",
"qwenlm": "QwenLM",
"silicon": "SiliconFlow",
"stepfun": "StepFun",
"together": "Together",
"yi": "零一萬物",
"zhinao": "360智腦",
"zhipu": "智譜AI",
"qwenlm": "QwenLM"
"infini": "無問芯穹",
"perplexity": "Perplexity",
"dmxapi": "DMXAPI"
},
"settings": {
"about": "關於與回饋",
"about.checkingUpdate": "正在檢查更新...",
"about.checkUpdate": "檢查更新",
"about.checkUpdate.available": "立即更新",
"about.checkingUpdate": "正在檢查更新...",
"about.contact.button": "郵件",
"about.contact.title": "聯繫方式",
"about.description": "一款為創作者而生的強大 AI 助手",
@ -388,13 +535,13 @@
"about.license.title": "許可證",
"about.releases.button": "查看",
"about.releases.title": "更新日誌",
"about.social.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": "預設助手",
@ -408,42 +555,64 @@
"success": "緩存清除成功",
"title": "清除緩存"
},
"data.app_data": "應用數據",
"data.app_logs": "應用日誌",
"data.title": "數據目錄",
"notion.api_key": "Notion 金鑰",
"notion.database_id": "Notion 資料庫 ID",
"notion.title": "Notion 配置",
"notion.check": {
"button": "檢查",
"fail": "連線失敗,請檢查配置",
"success": "連線成功",
"error": "連線異常,請檢查網路",
"empty_api_key": "未配置Api_key",
"empty_database_id": "未配置Database_id"
},
"title": "數據設定",
"webdav.autoSync": "自動備份",
"webdav.autoSync.off": "關閉",
"webdav.backup.button": "從 WebDAV 備份",
"webdav.host": "WebDAV 主機位址",
"webdav.host.placeholder": "http://localhost:8080",
"webdav.hours": "小時",
"webdav.lastSync": "上次同步時間",
"webdav.minutes": "分鐘",
"webdav.noSync": "等待下次備份",
"webdav.password": "WebDAV 密碼",
"webdav.path": "WebDAV Path",
"webdav.path.placeholder": "/backup",
"webdav.autoSync": "自動備份",
"webdav.minutes": "分鐘",
"webdav.hours": "小時",
"webdav.restore.button": "從 WebDAV 恢復",
"webdav.restore.content": "從 WebDAV 恢復將覆蓋當前資料,是否繼續?",
"webdav.restore.title": "從 WebDAV 恢復",
"webdav.syncError": "備份錯誤",
"webdav.syncStatus": "備份狀態",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV 使用者名稱",
"webdav.syncStatus": "備份狀態",
"webdav.autoSync.off": "關閉",
"webdav.noSync": "等待下次備份",
"webdav.syncError": "備份錯誤",
"webdav.lastSync": "上次同步時間",
"notion.api_key": "Notion 金鑰",
"notion.database_id": "Notion 資料庫 ID",
"notion.title": "Notion 配置"
},
"quickAssistant": {
"title": "快捷助手",
"click_tray_to_show": "點擊托盤圖標啟動",
"enable_quick_assistant": "啟用快捷助手",
"use_shortcut_to_show": "右鍵點擊托盤圖標或使用快捷鍵啟動"
"app_data": "應用數據",
"app_logs": "應用日誌"
},
"display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
"display.minApp.disabled": "隱藏的小程序",
"display.minApp.empty": "把要隱藏的小程序從左側拖拽到這裡",
"display.minApp.title": "小程序顯示設定",
"display.minApp.visible": "顯示的小程序",
"display.sidebar.chat.hiddenMessage": "助手是基礎功能,不支援隱藏",
"display.sidebar.disabled": "隱藏的圖標",
"display.sidebar.empty": "把要隱藏的功能從左側拖拽到這裡",
"display.sidebar.files.icon": "顯示文件圖示",
"display.sidebar.knowledge.icon": "顯示知識圖示",
"display.sidebar.minapp.icon": "顯示小程序圖示",
"display.sidebar.painting.icon": "顯示繪圖圖示",
"display.sidebar.title": "側邊欄設定",
"display.sidebar.translate.icon": "顯示翻譯圖示",
"display.sidebar.visible": "顯示的圖標",
"display.title": "顯示設定",
"display.topic.title": "話題設定",
"font_size.title": "訊息字體大小",
"general": "一般設定",
"general.backup.button": "備份",
"general.backup.title": "資料備份與復原",
"general.display.title": "顯示設定",
"general.manually_check_update.title": "關閉更新檢查",
"general.reset.button": "重置",
"general.reset.title": "資料重置",
@ -452,24 +621,6 @@
"general.user_name": "使用者名稱",
"general.user_name.placeholder": "輸入您的名稱",
"general.view_webdav_settings": "查看 WebDAV 設定",
"general.display.title": "顯示設定",
"display.sidebar.translate.icon": "顯示翻譯圖示",
"display.sidebar.painting.icon": "顯示繪圖圖示",
"display.sidebar.minapp.icon": "顯示小程序圖示",
"display.sidebar.knowledge.icon": "顯示知識圖示",
"display.sidebar.files.icon": "顯示文件圖示",
"display.sidebar.title": "側邊欄設定",
"display.topic.title": "話題設定",
"display.sidebar.chat.hiddenMessage": "助手是基礎功能,不支援隱藏",
"display.sidebar.empty": "把要隱藏的功能從左側拖拽到這裡",
"display.sidebar.visible": "顯示的圖標",
"display.sidebar.disabled": "隱藏的圖標",
"display.minApp.title": "小程序顯示設定",
"display.minApp.visible": "顯示的小程序",
"display.minApp.disabled": "隱藏的小程序",
"display.minApp.empty": "把要隱藏的小程序從左側拖拽到這裡",
"display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
"input.auto_translate_with_space": "快速敲擊3次空格翻譯",
"input.target_language": "目標語言",
"input.target_language.chinese": "簡體中文",
@ -479,16 +630,15 @@
"input.target_language.russian": "俄文",
"messages.divider": "訊息間顯示分隔線",
"messages.input.paste_long_text_as_file": "將長文本貼上為檔案",
"messages.input.paste_long_text_threshold": "長文本長度",
"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.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
"messages.model.title": "模型設定",
"messages.title": "訊息設定",
"messages.use_serif_font": "使用襯線字體",
"messages.input.paste_long_text_threshold": "長文本長度",
"model": "預設模型",
"models.add.add_model": "添加模型",
"models.add.group_name": "群組名稱",
@ -502,15 +652,15 @@
"models.default_assistant_model": "預設助手模型",
"models.default_assistant_model_description": "創建新助手時使用的模型,如果助手未設置模型,則使用此模型",
"models.empty": "找不到模型",
"models.enable_topic_naming": "話題自動重命名",
"models.topic_naming_model": "話題命名模型",
"models.topic_naming_model_description": "自動命名新話題時使用的模型",
"models.topic_naming_model_setting_title": "話題命名模型設定",
"models.topic_naming_prompt": "話題命名提示詞",
"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",
@ -523,6 +673,7 @@
"api_key": "API 密鑰",
"api_key.tip": "多個密鑰使用逗號分隔",
"api_version": "API 版本",
"charge": "充值",
"check": "檢查",
"check_all_keys": "檢查所有密鑰",
"check_multiple_keys": "檢查多個 API 密鑰",
@ -531,7 +682,6 @@
"docs_check": "檢查",
"docs_more_details": "查看更多細節",
"get_api_key": "點擊這裡獲取密鑰",
"charge": "充值",
"no_models": "請先添加模型再檢查 API 連接",
"not_checked": "未檢查",
"remove_duplicate_keys": "移除重複密鑰",
@ -549,28 +699,34 @@
"title": "代理設定"
},
"proxy.title": "代理地址",
"quickAssistant": {
"click_tray_to_show": "點擊托盤圖標啟動",
"enable_quick_assistant": "啟用快捷助手",
"title": "快捷助手",
"use_shortcut_to_show": "右鍵點擊托盤圖標或使用快捷鍵啟動"
},
"shortcuts": {
"action": "操作",
"alt_warning": "Mac 不能使用 Option + 字母作為快捷鍵",
"clear_shortcut": "清除快捷鍵",
"clear_topic": "清除所有訊息",
"copy_last_message": "複製上一条消息",
"key": "按鍵",
"mini_window": "快捷助手",
"new_topic": "新建話題",
"title": "快速方式",
"zoom_in": "放大界面",
"zoom_out": "縮小界面",
"zoom_reset": "重置縮放",
"show_app": "顯示應用",
"press_shortcut": "按下快捷鍵",
"reset_defaults": "重置預設快捷鍵",
"reset_defaults_confirm": "確定要重置所有快捷鍵嗎?",
"press_shortcut": "按下快捷鍵",
"alt_warning": "Mac 不能使用 Option + 字母作為快捷鍵",
"reset_to_default": "重置為預設",
"clear_shortcut": "清除快捷鍵",
"search_message": "搜索消息",
"show_app": "顯示應用",
"title": "快速方式",
"toggle_new_context": "清除上下文",
"toggle_show_assistants": "切換助手顯示",
"toggle_show_topics": "切換話題顯示",
"copy_last_message": "複製上一条消息",
"search_message": "搜索消息",
"mini_window": "快捷助手",
"clear_topic": "清除所有訊息",
"toggle_new_context": "清除上下文"
"zoom_in": "放大界面",
"zoom_out": "縮小界面",
"zoom_reset": "重置縮放"
},
"theme.auto": "自動",
"theme.dark": "深色主題",
@ -584,156 +740,37 @@
"topic.position.left": "左側",
"topic.position.right": "右側",
"topic.show.time": "顯示話題時間",
"tray.title": "啟用系統托盤圖標"
"tray.title": "啟用系統托盤圖標",
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息"
},
"translate": {
"any.language": "任意語言",
"button.translate": "翻譯",
"close": "關閉",
"confirm": {
"content": "翻譯後將覆蓋原文,是否繼續?",
"title": "翻譯確認"
},
"error.not_configured": "翻譯模型未配置",
"error.failed": "翻譯失敗",
"error.not_configured": "翻譯模型未配置",
"input.placeholder": "輸入文字進行翻譯",
"output.placeholder": "翻譯",
"processing": "翻譯中...",
"title": "翻譯",
"close": "關閉"
"title": "翻譯"
},
"tray": {
"quit": "退出",
"show_window": "顯示視窗",
"show_mini_window": "快捷助手"
"show_mini_window": "快捷助手",
"show_window": "顯示視窗"
},
"words": {
"knowledgeGraph": "知識圖譜",
"visualization": "可視化",
"quit": "退出",
"show_window": "顯示視窗",
"quit": "退出"
"visualization": "可視化"
},
"knowledge": {
"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": "知識庫數據庫引擎已更新,該知識庫將不再支持,請重新創建知識庫",
"no_provider": "知識庫模型提供商遺失,該知識庫將不再支持,請重新創建知識庫",
"source": "來源",
"chunk_size": "分段大小",
"chunk_overlap": "重疊大小",
"not_set": "未設置",
"settings": "知識庫設定",
"document_count": "請求文件數量",
"document_count_help": "請求文件數量越多,附帶的資訊越多,但需要消耗的 Token 也越多",
"document_count_default": "預設",
"chunk_size_placeholder": "預設值(不建議修改)",
"chunk_overlap_placeholder": "預設值(不建議修改)",
"chunk_size_tooltip": "將文件切割分段,每段的大小,不能超過模型上下文限制",
"chunk_overlap_tooltip": "相鄰文本塊之間重複的內容量,確保分段後的文本塊之間仍然有上下文聯繫,提升模型處理長文本的整體效果",
"chunk_size_change_warning": "分段大小和重疊大小修改只針對新添加的內容有效",
"chunk_size_too_large": "分段大小不能超過模型上下文限制({{max_context}}"
},
"models": {
"pinned": "已固定",
"search": "搜尋模型...",
"stream_output": "串流輸出",
"type": {
"select": "選擇模型類型",
"text": "文字",
"vision": "圖像",
"embedding": "嵌入",
"reasoning": "推理"
},
"all": "全部",
"vision": "視覺",
"websearch": "網路搜索",
"free": "免費",
"reasoning": "推理",
"embedding": "嵌入",
"embedding_model": "嵌入模型",
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
"dimensions": "{{dimensions}} 維",
"custom_parameters": "自定義參數",
"add_parameter": "添加參數",
"parameter_name": "參數名稱",
"parameter_type": {
"string": "文字",
"number": "數字",
"boolean": "布林值",
"json": "JSON"
}
},
"prompts": {
"title": "你是一名擅長會話的助理,你需要將用戶的會話總結為 10 個字以內的標題,標題語言與用戶的首要語言一致,不要使用標點符號和其他特殊符號",
"explanation": "幫我解釋一下這個概念",
"summarize": "幫我總結一下這段話"
},
"miniwindow": {
"feature": {
"chat": "回答此問題",
"translate": "文本翻譯",
"summary": "內容總結",
"explanation": "解釋說明"
},
"clipboard": {
"empty": "剪貼板為空"
},
"input": {
"placeholder": {
"title": "你想對下方文字做什麼",
"empty": "詢問 {{model}} 獲取幫助..."
}
},
"footer": {
"esc": "按 ESC {{action}}",
"esc_close": "關閉窗口",
"esc_back": "返回",
"copy_last_message": "按 C 鍵複製"
}
},
"auth": {
"oauth_button": "使用{{provider}}登入",
"get_key": "獲取",
"get_key_success": "自動獲取密鑰成功",
"login": "登入",
"error": "自動獲取密鑰失敗,請手動獲取"
"docs": {
"title": "幫助文件"
}
}
}

View File

@ -5,6 +5,7 @@ import EmojiPicker from '@renderer/components/EmojiPicker'
import { TopView } from '@renderer/components/TopView'
import { AGENT_PROMPT } from '@renderer/config/prompts'
import { useAgents } from '@renderer/hooks/useAgents'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { fetchGenerate } from '@renderer/services/ApiService'
import { getDefaultModel } from '@renderer/services/AssistantService'
import { useAppSelector } from '@renderer/store'
@ -14,6 +15,7 @@ import { Button, Form, FormInstance, Input, Modal, Popover, Select, SelectProps
import TextArea from 'antd/es/input/TextArea'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import stringWidth from 'string-width'
interface Props {
resolve: (data: Agent | null) => void
@ -36,6 +38,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [loading, setLoading] = useState(false)
const knowledgeState = useAppSelector((state) => state.knowledge)
const knowledgeOptions: SelectProps['options'] = []
const showKnowledgeIcon = useSidebarIconShow('knowledge')
knowledgeState.bases.forEach((base) => {
knowledgeOptions.push({
@ -104,6 +107,11 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
setLoading(false)
}
// Compute label width based on the longest label
const labelWidth = [t('agents.add.name'), t('agents.add.prompt'), t('agents.add.knowledge_base')]
.map((labelText) => stringWidth(labelText) * 8)
.reduce((maxWidth, currentWidth) => Math.max(maxWidth, currentWidth), 80)
return (
<Modal
title={t('agents.add.title')}
@ -117,7 +125,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<Form
ref={formRef}
form={form}
labelCol={{ flex: '80px' }}
labelCol={{ flex: `${labelWidth}px` }}
labelAlign="left"
colon={false}
style={{ marginTop: 25 }}
@ -145,14 +153,16 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
disabled={loading}
/>
</div>
<Form.Item name="knowledge_base_id" label={t('agents.add.knowledge_base')} rules={[{ required: false }]}>
<Select
allowClear
placeholder={t('agents.add.knowledge_base.placeholder')}
menuItemSelectedIcon={<CheckOutlined />}
options={knowledgeOptions}
/>
</Form.Item>
{showKnowledgeIcon && (
<Form.Item name="knowledge_base_id" label={t('agents.add.knowledge_base')} rules={[{ required: false }]}>
<Select
allowClear
placeholder={t('agents.add.knowledge_base.placeholder')}
menuItemSelectedIcon={<CheckOutlined />}
options={knowledgeOptions}
/>
</Form.Item>
)}
</Form>
</Modal>
)

View File

@ -16,6 +16,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { useShowTopics } from '@renderer/hooks/useStore'
import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
@ -65,8 +66,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
pasteLongTextThreshold,
showInputEstimatedTokens,
clickAssistantToShowTopic,
autoTranslateWithSpace,
sidebarIcons
autoTranslateWithSpace
} = useSettings()
const [expended, setExpend] = useState(false)
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
@ -85,11 +85,12 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const [isTranslating, setIsTranslating] = useState(false)
const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState<KnowledgeBase | undefined>(_base)
const [mentionModels, setMentionModels] = useState<Model[]>([])
const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false)
const isVision = useMemo(() => isVisionModel(model), [model])
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
const showKnowledgeIcon = sidebarIcons.visible.includes('knowledge')
const showKnowledgeIcon = useSidebarIconShow('knowledge')
const estimateTextTokens = useCallback(debounce(estimateTxtTokens, 1000), [])
const inputTokenCount = useMemo(
@ -165,6 +166,24 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
const isEnterPressed = event.keyCode == 13
if (event.key === '@') {
const textArea = textareaRef.current?.resizableTextArea?.textArea
if (textArea) {
const cursorPosition = textArea.selectionStart
const textBeforeCursor = text.substring(0, cursorPosition)
if (cursorPosition === 0 || textBeforeCursor.endsWith(' ')) {
EventEmitter.emit(EVENT_NAMES.SHOW_MODEL_SELECTOR)
setIsMentionPopupOpen(true)
return
}
}
}
if (event.key === 'Escape' && isMentionPopupOpen) {
setIsMentionPopupOpen(false)
return
}
if (autoTranslateWithSpace) {
if (event.key === ' ') {
setSpaceClickCount((prev) => prev + 1)
@ -193,25 +212,34 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
}
}
if (sendMessageShortcut === 'Enter' && isEnterPressed) {
if (event.shiftKey) {
return
if (isEnterPressed && !event.shiftKey && sendMessageShortcut === 'Enter') {
if (isMentionPopupOpen) {
return event.preventDefault()
}
sendMessage()
return event.preventDefault()
}
if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) {
if (isMentionPopupOpen) {
return event.preventDefault()
}
sendMessage()
return event.preventDefault()
}
if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) {
if (isMentionPopupOpen) {
return event.preventDefault()
}
sendMessage()
return event.preventDefault()
}
if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) {
if (isMentionPopupOpen) {
return event.preventDefault()
}
sendMessage()
return event.preventDefault()
}
@ -226,9 +254,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
await addAssistantMessagesToTopic({ assistant, topic })
// Reset to assistant default model
if (assistant.settings?.autoResetModel) {
assistant.defaultModel && setModel(assistant.defaultModel)
}
assistant.defaultModel && setModel(assistant.defaultModel)
addTopic(topic)
setActiveTopic(topic)
@ -280,6 +306,23 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const onInput = () => !expended && resizeTextArea()
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newText = e.target.value
setText(newText)
// Check if @ was deleted
const textArea = textareaRef.current?.resizableTextArea?.textArea
if (textArea) {
const cursorPosition = textArea.selectionStart
const textBeforeCursor = newText.substring(0, cursorPosition)
const lastAtIndex = textBeforeCursor.lastIndexOf('@')
if (lastAtIndex === -1 || textBeforeCursor.slice(lastAtIndex + 1).includes(' ')) {
setIsMentionPopupOpen(false)
}
}
}
const onPaste = useCallback(
async (event: ClipboardEvent) => {
const clipboardText = event.clipboardData?.getData('text')
@ -306,6 +349,11 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
if (supportExts.includes(getFileExtension(file.path))) {
const selectedFile = await window.api.file.get(file.path)
selectedFile && setFiles((prevFiles) => [...prevFiles, selectedFile])
} else {
window.message.info({
key: 'file_not_supported',
content: t('chat.input.file_not_supported')
})
}
}
}
@ -327,7 +375,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
}
}
},
[pasteLongTextAsFile, pasteLongTextThreshold, supportExts, text]
[pasteLongTextAsFile, pasteLongTextThreshold, supportExts, t, text]
)
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
@ -410,8 +458,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
}, [])
useEffect(() => {
setSelectedKnowledgeBase(assistant.knowledge_base)
}, [assistant.id, assistant.knowledge_base])
setSelectedKnowledgeBase(showKnowledgeIcon ? assistant.knowledge_base : undefined)
}, [assistant.id, assistant.knowledge_base, showKnowledgeIcon])
const textareaRows = window.innerHeight >= 1000 || isBubbleStyle ? 2 : 1
@ -420,17 +468,22 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
setSelectedKnowledgeBase(base)
}
const onMentionModel = useCallback(
(model: Model) => {
const isSelected = mentionModels.some((m) => m.id === model.id)
if (isSelected) {
setMentionModels(mentionModels.filter((m) => m.id !== model.id))
} else {
setMentionModels([...mentionModels, model])
const onMentionModel = (model: Model) => {
const textArea = textareaRef.current?.resizableTextArea?.textArea
if (textArea) {
const cursorPosition = textArea.selectionStart
const textBeforeCursor = text.substring(0, cursorPosition)
const lastAtIndex = textBeforeCursor.lastIndexOf('@')
if (lastAtIndex !== -1) {
const newText = text.substring(0, lastAtIndex) + text.substring(cursorPosition)
setText(newText)
}
},
[mentionModels]
)
setMentionModels((prev) => [...prev, model])
setIsMentionPopupOpen(false)
}
}
const handleRemoveModel = (model: Model) => {
setMentionModels(mentionModels.filter((m) => m.id !== model.id))
@ -447,7 +500,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
<MentionModelsInput selectedModels={mentionModels} onRemoveModel={handleRemoveModel} />
<Textarea
value={text}
onChange={(e) => setText(e.target.value)}
onChange={onChange}
onKeyDown={handleKeyDown}
placeholder={isTranslating ? t('chat.input.translating') : t('chat.input.placeholder')}
autoFocus
@ -458,7 +511,14 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
ref={textareaRef}
style={{ fontSize }}
styles={{ textarea: TextareaStyle }}
onFocus={() => setInputFocus(true)}
onFocus={(e: React.FocusEvent<HTMLTextAreaElement>) => {
setInputFocus(true)
const textArea = e.target
if (textArea) {
const length = textArea.value.length
textArea.setSelectionRange(length, length)
}
}}
onBlur={() => setInputFocus(false)}
onInput={onInput}
disabled={searching}

View File

@ -3,11 +3,12 @@ import ModelTags from '@renderer/components/ModelTags'
import { getModelLogo, isEmbeddingModel } from '@renderer/config/models'
import db from '@renderer/databases'
import { useProviders } from '@renderer/hooks/useProvider'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getModelUniqId } from '@renderer/services/ModelService'
import { Model } from '@renderer/types'
import { Model, Provider } from '@renderer/types'
import { Avatar, Dropdown, Tooltip } from 'antd'
import { first, sortBy } from 'lodash'
import { FC, useEffect, useState } from 'react'
import { FC, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { createGlobalStyle } from 'styled-components'
@ -17,18 +18,20 @@ interface Props {
ToolbarButton: any
}
const MentionModelsButton: FC<Props> = ({ onMentionModel: onSelect, ToolbarButton }) => {
const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelect, ToolbarButton }) => {
const { providers } = useProviders()
const [pinnedModels, setPinnedModels] = useState<string[]>([])
const { t } = useTranslation()
const dropdownRef = useRef<any>(null)
const [selectedIndex, setSelectedIndex] = useState(-1)
const [isOpen, setIsOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
const [searchText, setSearchText] = useState('')
const itemRefs = useRef<Array<HTMLDivElement | null>>([])
useEffect(() => {
const loadPinnedModels = async () => {
const setting = await db.settings.get('pinned:models')
setPinnedModels(setting?.value || [])
}
loadPinnedModels()
}, [])
const setItemRef = (index: number, el: HTMLDivElement | null) => {
itemRefs.current[index] = el
}
const togglePin = async (modelId: string) => {
const newPinnedModels = pinnedModels.includes(modelId)
@ -39,72 +42,263 @@ const MentionModelsButton: FC<Props> = ({ onMentionModel: onSelect, ToolbarButto
setPinnedModels(newPinnedModels)
}
const modelMenuItems = providers
.filter((p) => p.models && p.models.length > 0)
.map((p) => {
const filteredModels = sortBy(p.models, ['group', 'name'])
.filter((m) => !isEmbeddingModel(m))
const handleModelSelect = (model: Model) => {
// Check if model is already selected
if (mentionModels.some((selected) => selected.id === model.id)) {
return
}
onSelect(model)
setIsOpen(false)
}
const modelMenuItems = useMemo(() => {
const items = providers
.filter((p) => p.models && p.models.length > 0)
.map((p) => {
const filteredModels = sortBy(p.models, ['group', 'name'])
.filter((m) => !isEmbeddingModel(m))
// Filter out pinned models from regular groups
.filter((m) => !pinnedModels.includes(getModelUniqId(m)))
// Filter by search text
.filter((m) => {
if (!searchText) return true
return (
m.name.toLowerCase().includes(searchText.toLowerCase()) ||
m.id.toLowerCase().includes(searchText.toLowerCase())
)
})
.map((m) => ({
key: getModelUniqId(m),
model: m,
label: (
<ModelItem>
<ModelNameRow>
<span>{m?.name}</span> <ModelTags model={m} />
</ModelNameRow>
<PinIcon
onClick={(e) => {
e.stopPropagation()
togglePin(getModelUniqId(m))
}}
$isPinned={pinnedModels.includes(getModelUniqId(m))}>
<PushpinOutlined />
</PinIcon>
</ModelItem>
),
icon: (
<Avatar src={getModelLogo(m.id)} size={24}>
{first(m.name)}
</Avatar>
),
onClick: () => handleModelSelect(m)
}))
return filteredModels.length > 0
? {
key: p.id,
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
type: 'group' as const,
children: filteredModels
}
: null
})
.filter((group): group is NonNullable<typeof group> => group !== null)
if (pinnedModels.length > 0) {
const pinnedItems = providers
.filter((p): p is Provider => p.models && p.models.length > 0)
.flatMap((p) =>
p.models
.filter((m) => pinnedModels.includes(getModelUniqId(m)))
.map((m) => ({
key: getModelUniqId(m),
model: m,
provider: p
}))
)
.map((m) => ({
key: getModelUniqId(m),
...m,
key: m.key + 'pinned',
label: (
<ModelItem>
<ModelNameRow>
<span>{m?.name}</span> <ModelTags model={m} />
<span>
{m.model?.name} | {m.provider.isSystem ? t(`provider.${m.provider.id}`) : m.provider.name}
</span>{' '}
<ModelTags model={m.model} />
</ModelNameRow>
{/* <Checkbox checked={selectedModels.some((sm) => sm.id === m.id)} /> */}
<PinIcon
onClick={(e) => {
e.stopPropagation()
togglePin(getModelUniqId(m))
togglePin(getModelUniqId(m.model))
}}
$isPinned={pinnedModels.includes(getModelUniqId(m))}>
$isPinned={true}>
<PushpinOutlined />
</PinIcon>
</ModelItem>
),
icon: (
<Avatar src={getModelLogo(m.id)} size={24}>
{first(m.name)}
<Avatar src={getModelLogo(m.model.id)} size={24}>
{first(m.model.name)}
</Avatar>
),
onClick: () => {
onSelect(m)
}
onClick: () => handleModelSelect(m.model)
}))
return filteredModels.length > 0
? {
key: p.id,
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
type: 'group' as const,
children: filteredModels
}
: null
})
.filter(Boolean)
if (pinnedModels.length > 0) {
const pinnedItems = modelMenuItems
.flatMap((p) => p?.children || [])
.filter((m) => pinnedModels.includes(m.key))
.map((m) => ({ ...m, key: m.key + 'pinned' }))
if (pinnedItems.length > 0) {
modelMenuItems.unshift({
key: 'pinned',
label: t('models.pinned'),
type: 'group' as const,
children: pinnedItems
})
if (pinnedItems.length > 0) {
items.unshift({
key: 'pinned',
label: t('models.pinned'),
type: 'group' as const,
children: pinnedItems
})
}
}
// Remove empty groups
return items.filter((group) => group.children.length > 0)
}, [providers, pinnedModels, t, onSelect, mentionModels, searchText])
// Get flattened list of all model items
const flatModelItems = useMemo(() => {
return modelMenuItems.flatMap((group) => group?.children || [])
}, [modelMenuItems])
useEffect(() => {
const loadPinnedModels = async () => {
const setting = await db.settings.get('pinned:models')
setPinnedModels(setting?.value || [])
}
loadPinnedModels()
}, [])
// Scroll to the first menu item when the mode selection menu opens
useLayoutEffect(() => {
if (isOpen && flatModelItems.length > 0 && itemRefs.current[0]) {
itemRefs.current[0].scrollIntoView({ block: 'nearest' })
}
}, [isOpen, flatModelItems])
useEffect(() => {
const showModelSelector = () => {
dropdownRef.current?.click()
itemRefs.current = []
setIsOpen(true)
setSelectedIndex(0)
setSearchText('')
}
const handleKeyDown = (e: KeyboardEvent) => {
if (!isOpen) return
if (e.key === 'ArrowDown') {
e.preventDefault()
setSelectedIndex((prev) => {
const newIndex = prev < flatModelItems.length - 1 ? prev + 1 : 0
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
return newIndex
})
} else if (e.key === 'ArrowUp') {
e.preventDefault()
setSelectedIndex((prev) => {
const newIndex = prev > 0 ? prev - 1 : flatModelItems.length - 1
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
return newIndex
})
} else if (e.key === 'Enter') {
e.preventDefault()
if (selectedIndex >= 0 && selectedIndex < flatModelItems.length) {
const selectedModel = flatModelItems[selectedIndex].model
if (!mentionModels.some((selected) => selected.id === selectedModel.id)) {
flatModelItems[selectedIndex].onClick()
}
setIsOpen(false)
setSearchText('')
}
} else if (e.key === 'Escape') {
setIsOpen(false)
setSearchText('')
}
}
const handleTextChange = (e: Event) => {
const textArea = e.target as HTMLTextAreaElement
const cursorPosition = textArea.selectionStart
const textBeforeCursor = textArea.value.substring(0, cursorPosition)
const lastAtIndex = textBeforeCursor.lastIndexOf('@')
if (lastAtIndex === -1 || textBeforeCursor.slice(lastAtIndex + 1).includes(' ')) {
setIsOpen(false)
setSearchText('')
} else if (lastAtIndex !== -1) {
// Get the text after @ for search
const searchStr = textBeforeCursor.slice(lastAtIndex + 1)
setSearchText(searchStr)
}
}
const textArea = document.querySelector('.inputbar textarea') as HTMLTextAreaElement
if (textArea) {
textArea.addEventListener('input', handleTextChange)
}
EventEmitter.on(EVENT_NAMES.SHOW_MODEL_SELECTOR, showModelSelector)
document.addEventListener('keydown', handleKeyDown)
return () => {
EventEmitter.off(EVENT_NAMES.SHOW_MODEL_SELECTOR, showModelSelector)
document.removeEventListener('keydown', handleKeyDown)
if (textArea) {
textArea.removeEventListener('input', handleTextChange)
}
}
}, [isOpen, selectedIndex, flatModelItems, mentionModels])
// Hide dropdown if no models available
if (flatModelItems.length === 0) {
return null
}
const menu = (
<div ref={menuRef} className="ant-dropdown-menu">
{modelMenuItems.map((group, groupIndex) => {
if (!group) return null
// Calculate the starting index for this group's items
const startIndex = modelMenuItems.slice(0, groupIndex).reduce((acc, g) => acc + (g?.children?.length || 0), 0)
return (
<div key={group.key} className="ant-dropdown-menu-item-group">
<div className="ant-dropdown-menu-item-group-title">{group.label}</div>
<div>
{group.children.map((item, idx) => (
<div
key={item.key}
ref={(el) => setItemRef(startIndex + idx, el)}
className={`ant-dropdown-menu-item ${selectedIndex === startIndex + idx ? 'ant-dropdown-menu-item-selected' : ''}`}
onClick={item.onClick}>
<span className="ant-dropdown-menu-item-icon">{item.icon}</span>
{item.label}
</div>
))}
</div>
</div>
)
})}
</div>
)
return (
<>
<DropdownMenuStyle />
<Dropdown menu={{ items: modelMenuItems }} trigger={['click']} overlayClassName="mention-models-dropdown">
<Dropdown
dropdownRender={() => menu}
trigger={['click']}
open={isOpen}
onOpenChange={setIsOpen}
overlayClassName="mention-models-dropdown">
<Tooltip placement="top" title={t('agents.edit.model.select.title')} arrow>
<ToolbarButton type="text">
<ToolbarButton type="text" ref={dropdownRef}>
<i className="iconfont icon-at" style={{ fontSize: 18 }}></i>
</ToolbarButton>
</Tooltip>
@ -117,6 +311,59 @@ const DropdownMenuStyle = createGlobalStyle`
.mention-models-dropdown {
.ant-dropdown-menu {
max-height: 400px;
overflow-y: auto;
overflow-x: hidden;
padding: 4px 0;
margin-bottom: 40px;
position: relative;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background: var(--color-scrollbar-thumb);
&:hover {
background: var(--color-scrollbar-thumb-hover);
}
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.ant-dropdown-menu-item-group {
.ant-dropdown-menu-item-group-title {
padding: 5px 12px;
color: var(--color-text-3);
font-size: 12px;
}
}
.ant-dropdown-menu-item {
padding: 5px 12px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
&:hover {
background: var(--color-hover);
}
&.ant-dropdown-menu-item-selected {
background-color: var(--color-primary-bg);
color: var(--color-primary);
}
.ant-dropdown-menu-item-icon {
margin-right: 0;
}
}
}
`
@ -127,6 +374,7 @@ const ModelItem = styled.div`
justify-content: space-between;
font-size: 14px;
width: 100%;
min-width: 200px;
gap: 16px;
&:hover {

View File

@ -1,17 +1,27 @@
import { useProviders } from '@renderer/hooks/useProvider'
import { Model } from '@renderer/types'
import { Flex, Tag } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const MentionModelsInput: FC<{
selectedModels: Model[]
onRemoveModel: (model: Model) => void
}> = ({ selectedModels, onRemoveModel }) => {
const { providers } = useProviders()
const { t } = useTranslation()
const getProviderName = (model: Model) => {
const provider = providers.find((p) => p.models?.some((m) => m.id === model.id))
return provider ? (provider.isSystem ? t(`provider.${provider.id}`) : provider.name) : ''
}
return (
<Container gap="4px 0" wrap>
{selectedModels.map((model) => (
<Tag bordered={false} color="processing" key={model.id} closable onClose={() => onRemoveModel(model)}>
@{model.name}
@{model.name} ({getProviderName(model)})
</Tag>
))}
</Container>

View File

@ -3,6 +3,7 @@ import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useModel } from '@renderer/hooks/useModel'
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
import { useTopic } from '@renderer/hooks/useTopic'
import { fetchChatCompletion } from '@renderer/services/ApiService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getMessageModelId } from '@renderer/services/MessagesService'
@ -43,7 +44,7 @@ const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolea
const MessageItem: FC<Props> = ({
message: _message,
topic,
topic: _topic,
index,
hidePresetMessages,
isGrouped,
@ -59,6 +60,7 @@ const MessageItem: FC<Props> = ({
const { isBubbleStyle } = useMessageStyle()
const { showMessageDivider, messageFont, fontSize } = useSettings()
const messageContainerRef = useRef<HTMLDivElement>(null)
const topic = useTopic(assistant, _topic?.id)
const isLastMessage = index === 0
const isAssistantMessage = message.role === 'assistant'
@ -121,6 +123,12 @@ const MessageItem: FC<Props> = ({
const messages = onGetMessages()
const assistantWithModel = message.model ? { ...assistant, model: message.model } : assistant
if (topic.prompt) {
assistantWithModel.prompt = assistantWithModel.prompt
? `${assistantWithModel.prompt}\n${topic.prompt}`
: topic.prompt
}
fetchChatCompletion({
message,
messages: messages

View File

@ -150,11 +150,11 @@ const CitationLink = styled.a`
line-height: 1.6;
text-decoration: none;
color: var(--color-text-1);
.hostname {
color: var(--color-link);
}
&:hover {
text-decoration: underline;
}

View File

@ -197,7 +197,8 @@ const MessageMenubar: FC<Props> = (props) => {
const onRegenerate = async () => {
await modelGenerating()
const _message: Message = resetAssistantMessage(message, model || assistantModel)
const selectedModel = isGrouped ? model : assistantModel
const _message = resetAssistantMessage(message, selectedModel)
onEditMessage?.(_message)
}

View File

@ -1,10 +1,11 @@
import { Message } from '@renderer/types'
import { Collapse } from 'antd'
import { FC, useEffect, useState } from 'react'
import { FC, useEffect, useState, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ReactMarkdown from 'react-markdown'
import BarLoader from 'react-spinners/BarLoader'
import styled from 'styled-components'
import Markdown from '../Markdown/Markdown'
import { useSettings } from '@renderer/hooks/useSettings'
interface Props {
message: Message
@ -14,6 +15,12 @@ const MessageThought: FC<Props> = ({ message }) => {
const [activeKey, setActiveKey] = useState<'thought' | ''>('thought')
const isThinking = !message.content
const { t } = useTranslation()
const { messageFont, fontSize } = useSettings()
const fontFamily = useMemo(() => {
return messageFont === 'serif'
? 'serif'
: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans","Helvetica Neue", sans-serif'
}, [messageFont])
useEffect(() => {
if (!isThinking) setActiveKey('')
@ -42,7 +49,11 @@ const MessageThought: FC<Props> = ({ message }) => {
{isThinking && <BarLoader color="#9254de" />}
</MessageTitleLabel>
),
children: <ReactMarkdown className="markdown">{message.reasoning_content}</ReactMarkdown>
children: (
<div style={{ fontFamily, fontSize }}>
<Markdown message={{ ...message, content: message.reasoning_content }} />
</div>
)
}
]}
/>

View File

@ -315,7 +315,7 @@ const Messages: FC<Props> = ({ assistant, topic, setActiveTopic }) => {
))}
</ScrollContainer>
</InfiniteScroll>
<Prompt assistant={assistant} key={assistant.prompt} />
<Prompt assistant={assistant} key={assistant.prompt} topic={topic} />
</NarrowLayout>
</Container>
)
@ -349,8 +349,7 @@ interface ContainerProps {
const Container = styled(Scrollbar)<ContainerProps>`
display: flex;
flex-direction: column-reverse;
padding: 10px 0;
padding-bottom: 20px;
padding: 10px 0 20px;
overflow-x: hidden;
background-color: var(--color-background);
`

View File

@ -1,22 +1,22 @@
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { Assistant } from '@renderer/types'
import { Assistant, Topic } from '@renderer/types'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props {
assistant: Assistant
topic?: Topic
}
const Prompt: FC<Props> = ({ assistant }) => {
const Prompt: FC<Props> = ({ assistant, topic }) => {
const { t } = useTranslation()
const prompt = assistant.prompt || t('chat.default.description')
if (!prompt) {
const topicPrompt = topic?.prompt || ''
if (!prompt && !topicPrompt) {
return null
}
return (
<Container className="system-prompt" onClick={() => AssistantSettingsPopup.show({ assistant })}>
<Text>{prompt}</Text>

View File

@ -30,6 +30,7 @@ import {
setShowMessageDivider
} from '@renderer/store/settings'
import { Assistant, AssistantSettings, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
import { modalConfirm } from '@renderer/utils'
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -106,7 +107,6 @@ const SettingsTab: FC<Props> = (props) => {
maxTokens: DEFAULT_MAX_TOKENS,
streamOutput: true,
hideMessages: false,
autoResetModel: false,
customParameters: []
}
})
@ -178,7 +178,7 @@ const SettingsTab: FC<Props> = (props) => {
/>
</SettingRow>
<SettingDivider />
<Row align="middle" justify="space-between">
<Row align="middle" justify="space-between" style={{ marginBottom: 10 }}>
<HStack alignItems="center">
<Label>{t('chat.settings.max_tokens')}</Label>
<Tooltip title={t('chat.settings.max_tokens.tip')}>
@ -188,25 +188,39 @@ const SettingsTab: FC<Props> = (props) => {
<Switch
size="small"
checked={enableMaxTokens}
onChange={(enabled) => {
onChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),
content: t('chat.settings.max_tokens.confirm_content'),
okButtonProps: {
danger: true
}
})
if (!confirmed) return
}
setEnableMaxTokens(enabled)
onUpdateAssistantSettings({ 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>
{enableMaxTokens && (
<Row align="middle" gutter={10}>
<Col span={24}>
<InputNumber
disabled={!enableMaxTokens}
min={0}
max={10000000}
step={100}
value={typeof maxTokens === 'number' ? maxTokens : 0}
changeOnBlur
onChange={(value) => value && setMaxTokens(value)}
onBlur={() => onMaxTokensChange(maxTokens)}
style={{ width: '100%' }}
/>
</Col>
</Row>
)}
</SettingGroup>
<SettingGroup>
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>

View File

@ -5,7 +5,9 @@ import {
EditOutlined,
FolderOutlined,
PushpinOutlined,
UploadOutlined} from '@ant-design/icons'
QuestionCircleOutlined,
UploadOutlined
} from '@ant-design/icons'
import DragableList from '@renderer/components/DragableList'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import Scrollbar from '@renderer/components/Scrollbar'
@ -19,7 +21,7 @@ import store from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime'
import { Assistant, Topic } from '@renderer/types'
import { exportTopicAsMarkdown, exportTopicToNotion, topicToMarkdown } from '@renderer/utils/export'
import { Dropdown, MenuProps } from 'antd'
import { Dropdown, MenuProps, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { findIndex } from 'lodash'
import { FC, useCallback } from 'react'
@ -114,6 +116,28 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
}
}
},
{
label: t('chat.topics.prompt'),
key: 'topic-prompt',
icon: <i className="iconfont icon-ai-model1" style={{ fontSize: '14px' }} />,
extra: (
<Tooltip title={t('chat.topics.prompt.tips')}>
<QuestionIcon />
</Tooltip>
),
async onClick() {
const prompt = await PromptPopup.show({
title: t('chat.topics.prompt.edit.title'),
message: '',
defaultValue: topic?.prompt || '',
inputProps: {
rows: 8,
allowClear: true
}
})
prompt && updateTopic({ ...topic, prompt: prompt.trim() })
}
},
{
label: topic.pinned ? t('chat.topics.unpinned') : t('chat.topics.pinned'),
key: 'pin',
@ -149,7 +173,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
key: 'markdown',
onClick: () => exportTopicAsMarkdown(topic)
},
{
label: t('chat.topics.export.word'),
key: 'word',
@ -162,7 +186,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
label: t('chat.topics.export.notion'),
key: 'notion',
onClick: () => exportTopicToNotion(topic)
},
}
]
}
]
@ -210,6 +234,11 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
onClick={() => onSwitchTopic(topic)}
style={{ borderRadius }}>
<TopicName className="name">{topic.name.replace('`', '')}</TopicName>
{topic.prompt && (
<TopicPromptText className="prompt">
{t('common.prompt')}: {topic.prompt}
</TopicPromptText>
)}
{showTopicTime && (
<TopicTime className="time">{dayjs(topic.createdAt).format('MM/DD HH:mm')}</TopicTime>
)}
@ -219,9 +248,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
className="menu"
onClick={(e) => {
e.stopPropagation()
if (assistant.topics.length === 1) {
return onClearMessages()
}
if (assistant.topics.length === 1) {
return onClearMessages()
}
onDeleteTopic(topic)
}}>
<CloseOutlined />
@ -290,6 +319,18 @@ const TopicName = styled.div`
font-size: 13px;
`
const TopicPromptText = styled.div`
color: var(--color-text-2);
font-size: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
~ .prompt-text {
margin-top: 10px;
}
`
const TopicTime = styled.div`
color: var(--color-text-3);
font-size: 11px;
@ -309,5 +350,10 @@ const MenuButton = styled.div`
font-size: 12px;
}
`
const QuestionIcon = styled(QuestionCircleOutlined)`
font-size: 14px;
cursor: pointer;
color: var(--color-text-3);
`
export default Topics

View File

@ -10,6 +10,7 @@ import {
SearchOutlined,
SettingOutlined
} from '@ant-design/icons'
import Ellipsis from '@renderer/components/Ellipsis'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import Scrollbar from '@renderer/components/Scrollbar'
@ -17,7 +18,8 @@ import { useKnowledge } from '@renderer/hooks/useKnowledge'
import FileManager from '@renderer/services/FileManager'
import { getProviderName } from '@renderer/services/ProviderService'
import { FileType, FileTypes, KnowledgeBase } from '@renderer/types'
import { Alert, Button, Card, Divider, message, Tag, Typography, Upload } from 'antd'
import { bookExts, documentExts, textExts } from '@shared/config/constant'
import { Alert, Button, Card, Divider, message, Tag, Tooltip, Typography, Upload } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -33,10 +35,10 @@ interface KnowledgeContentProps {
selectedBase: KnowledgeBase
}
const fileTypes = ['.pdf', '.docx', '.pptx', '.xlsx', '.txt', '.md', '.html', '.csv']
const fileTypes = [...bookExts, ...documentExts, ...textExts]
const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
const { t } = useTranslation()
const {
base,
noteItems,
@ -105,26 +107,32 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
return
}
const url = await PromptPopup.show({
const urlInput = await PromptPopup.show({
title: t('knowledge.add_url'),
message: '',
inputPlaceholder: t('knowledge.url_placeholder'),
inputProps: {
maxLength: 1000,
rows: 1
rows: 10,
onPressEnter: () => {}
}
})
if (url) {
try {
new URL(url)
if (urlItems.find((item) => item.content === url)) {
message.success(t('knowledge.url_added'))
return
if (urlInput) {
// Split input by newlines and filter out empty lines
const urls = urlInput.split('\n').filter((url) => url.trim())
for (const url of urls) {
try {
new URL(url.trim())
if (!urlItems.find((item) => item.content === url.trim())) {
addUrl(url.trim())
} else {
message.success(t('knowledge.url_added'))
}
} catch (e) {
// Skip invalid URLs silently
continue
}
addUrl(url)
} catch (e) {
console.error('Invalid URL:', url)
}
}
}
@ -209,7 +217,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
style={{ marginTop: 10, background: 'transparent' }}>
<p className="ant-upload-text">{t('knowledge.drag_file')}</p>
<p className="ant-upload-hint">
{t('knowledge.file_hint', { file_types: fileTypes.join(', ').replaceAll('.', '') })}
{t('knowledge.file_hint', { file_types: fileTypes.slice(0, 6).join(', ').replaceAll('.', '') })}
</p>
</Dragger>
</FileSection>
@ -222,7 +230,11 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemContent>
<ItemInfo>
<FileIcon />
<ClickableSpan onClick={() => window.api.file.openPath(file.path)}>{file.origin_name}</ClickableSpan>
<ClickableSpan onClick={() => window.api.file.openPath(file.path)}>
<Tooltip title={file.origin_name}>
<Ellipsis text={file.origin_name} />
</Tooltip>
</ClickableSpan>
</ItemInfo>
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
@ -251,7 +263,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemInfo>
<FolderOutlined />
<ClickableSpan onClick={() => window.api.file.openPath(item.content as string)}>
{item.content as string}
<Tooltip title={item.content as string}>
<Ellipsis text={item.content as string} />
</Tooltip>
</ClickableSpan>
</ItemInfo>
<FlexAlignCenter>
@ -281,7 +295,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemInfo>
<LinkOutlined />
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.content as string}
<Tooltip title={item.content as string}>
<Ellipsis text={item.content as string} />
</Tooltip>
</a>
</ItemInfo>
<FlexAlignCenter>
@ -311,7 +327,9 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemInfo>
<GlobalOutlined />
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.content as string}
<Tooltip title={item.content as string}>
<Ellipsis text={item.content as string} />
</Tooltip>
</a>
</ItemInfo>
<FlexAlignCenter>

View File

@ -1,5 +1,6 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { TopView } from '@renderer/components/TopView'
import { DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant'
import { getFileFromUrl, getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
import { FileType, KnowledgeBase } from '@renderer/types'
import { Input, List, Modal, Spin, Typography } from 'antd'
@ -45,7 +46,11 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
return { ...item, file }
})
)
setResults(results)
const filteredResults = results.filter((item) => {
const threshold = base.threshold || DEFAULT_KNOWLEDGE_THRESHOLD
return item.score >= threshold
})
setResults(filteredResults)
} catch (error) {
console.error('Search failed:', error)
} finally {

View File

@ -22,6 +22,7 @@ interface FormData {
documentCount?: number
chunkSize?: number
chunkOverlap?: number
threshold?: number
}
interface Props extends ShowParams {
@ -66,7 +67,8 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
name: values.name,
documentCount: values.documentCount || DEFAULT_KNOWLEDGE_DOCUMENT_COUNT,
chunkSize: values.chunkSize,
chunkOverlap: values.chunkOverlap
chunkOverlap: values.chunkOverlap,
threshold: values.threshold ?? undefined
}
updateKnowledgeBase(newBase)
setOpen(false)
@ -121,9 +123,9 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
<Slider
style={{ width: '100%' }}
min={1}
max={15}
max={30}
step={1}
marks={{ 1: '1', 6: t('knowledge.document_count_default'), 15: '15' }}
marks={{ 1: '1', 6: t('knowledge.document_count_default'), 30: '30' }}
/>
</Form.Item>
@ -174,6 +176,23 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
placeholder={t('knowledge.chunk_overlap_placeholder')}
/>
</Form.Item>
<Form.Item
name="threshold"
label={t('knowledge.threshold')}
tooltip={{ title: t('knowledge.threshold_tooltip') }}
initialValue={base.threshold}
rules={[
{
validator(_, value) {
if (value && (value > 1 || value < 0)) {
return Promise.reject(new Error(t('knowledge.threshold_too_large_or_small')))
}
return Promise.resolve()
}
}
]}>
<InputNumber placeholder={t('knowledge.threshold_placeholder')} step={0.1} style={{ width: '100%' }} />
</Form.Item>
</Form>
<Alert message={t('knowledge.chunk_size_change_warning')} type="warning" showIcon icon={<WarningOutlined />} />
</Modal>

View File

@ -5,6 +5,7 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { SettingRow } from '@renderer/pages/settings'
import { Assistant, AssistantSettingCustomParameters, AssistantSettings } from '@renderer/types'
import { modalConfirm } from '@renderer/utils'
import { Button, Col, Divider, Input, InputNumber, Radio, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { isNull } from 'lodash'
import { FC, useEffect, useRef, useState } from 'react'
@ -22,7 +23,6 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
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 [reasoningEffort, setReasoningEffort] = useState(assistant?.settings?.reasoning_effort ?? 'medium')
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
@ -54,12 +54,6 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
}
}
const onMaxTokensChange = (value) => {
if (!isNaN(value as number)) {
updateAssistantSettings({ maxTokens: value })
}
}
const onTopPChange = (value) => {
if (!isNaN(value as number)) {
updateAssistantSettings({ topP: value })
@ -192,32 +186,27 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Row align="middle" style={{ marginBottom: 10 }}>
<Label style={{ marginBottom: 10 }}>{t('assistants.settings.default_model')}</Label>
<Col span={24}>
<HStack alignItems="center">
<HStack alignItems="center" gap={5}>
<Button
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusOutlined />}
onClick={onSelectModel}>
{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}
</Button>
{defaultModel && (
<Button
icon={<DeleteOutlined />}
type="text"
onClick={() => {
setDefaultModel(undefined)
updateAssistant({ ...assistant, defaultModel: undefined })
}}
danger
/>
)}
</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)
setTimeout(() => updateAssistantSettings({ autoResetModel: checked }), 500)
}}
/>
</SettingRow>
<Divider style={{ margin: '10px 0' }} />
<Row align="middle">
<Label>{t('chat.settings.temperature')}</Label>
<Tooltip title={t('chat.settings.temperature.tip')}>
@ -335,34 +324,30 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
</HStack>
<Switch
checked={enableMaxTokens}
onChange={(enabled) => {
onChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),
content: t('chat.settings.max_tokens.confirm_content'),
okButtonProps: {
danger: true
}
})
if (!confirmed) return
}
setEnableMaxTokens(enabled)
updateAssistantSettings({ enableMaxTokens: enabled })
}}
/>
</SettingRow>
{enableMaxTokens && (
<Row align="middle" gutter={20}>
<Col span={20}>
<Slider
disabled={!enableMaxTokens}
min={0}
max={32000}
onChange={setMaxTokens}
onChangeComplete={onMaxTokensChange}
value={typeof maxTokens === 'number' ? maxTokens : 0}
step={50}
marks={{
0: '0',
32000: t('chat.settings.max')
}}
/>
</Col>
<Col span={4}>
<Row align="middle" style={{ marginTop: 5, marginBottom: 5 }}>
<Col span={24}>
<InputNumber
disabled={!enableMaxTokens}
min={0}
max={32000}
max={10000000}
step={100}
value={maxTokens}
changeOnBlur

View File

@ -2,6 +2,7 @@ import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { useAgent } from '@renderer/hooks/useAgents'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { Assistant } from '@renderer/types'
import { Menu, Modal } from 'antd'
import { useState } from 'react'
@ -34,6 +35,8 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props })
const updateAssistant = isAgent ? _useAgent.updateAgent : _useAssistant.updateAssistant
const updateAssistantSettings = isAgent ? _useAgent.updateAgentSettings : _useAssistant.updateAssistantSettings
const showKnowledgeIcon = useSidebarIconShow('knowledge')
const onOk = () => {
setOpen(false)
}
@ -59,11 +62,11 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props })
key: 'messages',
label: t('assistants.settings.preset_messages')
},
{
showKnowledgeIcon && {
key: 'knowledge_base',
label: t('assistants.settings.knowledge_base')
}
]
].filter(Boolean) as { key: string; label: string }[]
return (
<StyledModal
@ -120,7 +123,7 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, ...props })
updateAssistantSettings={updateAssistantSettings}
/>
)}
{menu === 'knowledge_base' && (
{menu === 'knowledge_base' && showKnowledgeIcon && (
<AssistantKnowledgeBaseSettings
assistant={assistant}
updateAssistant={updateAssistant}

View File

@ -1,11 +1,12 @@
import { FileSearchOutlined, FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
import { Client } from '@notionhq/client'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { backup, reset, restore } from '@renderer/services/BackupService'
import { RootState, useAppDispatch } from '@renderer/store'
import { setNotionApiKey, setNotionDatabaseID } from '@renderer/store/settings'
import { AppInfo } from '@renderer/types'
import { Button,Modal, Typography } from 'antd'
import { Button, Modal, Typography } from 'antd'
import Input from 'antd/es/input/Input'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -23,35 +24,46 @@ const NotionSettings: FC = () => {
// 这里可以添加 Notion 相关的状态和逻辑
// 例如:
const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey);
const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID);
const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey)
const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID)
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionApiKey(e.target.value))
};
}
const handleNotionDatabaseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionDatabaseID(e.target.value))
};
}
const handleNotionConnectionCheck = () => {
if (notionApiKey === null) {
window.message.error(t('settings.data.notion.check.empty_api_key'))
return
}
if (notionDatabaseID === null) {
window.message.error(t('settings.data.notion.check.empty_database_id'))
return
}
const notion = new Client({ auth: notionApiKey })
notion.databases
.retrieve({
database_id: notionDatabaseID
})
.then((result) => {
if (result) {
window.message.success(t('settings.data.notion.check.success'))
} else {
window.message.error(t('settings.data.notion.check.fail'))
}
})
.catch(() => {
window.message.error(t('settings.data.notion.check.error'))
})
}
return (
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.notion.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
<Input.Password
type="text"
value={notionApiKey || ''}
onChange={handleNotionTokenChange}
onBlur={handleNotionTokenChange}
style={{ width: 250 }}
/>
</HStack>
</SettingRow>
<SettingDivider /> {/* 添加分割线 */}
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.database_id')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
@ -60,10 +72,27 @@ const NotionSettings: FC = () => {
value={notionDatabaseID || ''}
onChange={handleNotionDatabaseIdChange}
onBlur={handleNotionDatabaseIdChange}
style={{ width: 250 }}
style={{ width: 315 }}
/>
</HStack>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
<Input
type="password"
value={notionApiKey || ''}
onChange={handleNotionTokenChange}
onBlur={handleNotionTokenChange}
style={{ width: 250 }}
/>
<Button onClick={handleNotionConnectionCheck} style={{ width: 60 }}>
{t('settings.data.notion.check.button')}
</Button>
</HStack>
</SettingRow>
<SettingDivider /> {/* 添加分割线 */}
</SettingGroup>
)
}
@ -165,7 +194,6 @@ const DataSettings: FC = () => {
</HStack>
</SettingRow>
</SettingGroup>
</SettingContainer>
)
}

View File

@ -66,6 +66,15 @@ const WebDavSettings: FC = () => {
setRestoring(false)
}
const onPressRestore = () => {
window.modal.confirm({
title: t('settings.data.webdav.restore.title'),
content: t('settings.data.webdav.restore.content'),
centered: true,
onOk: onRestore
})
}
const onSyncIntervalChange = (value: number) => {
setSyncInterval(value)
dispatch(_setWebdavSyncInterval(value))
@ -158,7 +167,7 @@ const WebDavSettings: FC = () => {
<Button onClick={onBackup} icon={<SaveOutlined />} loading={backuping}>
{t('settings.data.webdav.backup.button')}
</Button>
<Button onClick={onRestore} icon={<FolderOpenOutlined />} loading={restoring}>
<Button onClick={onPressRestore} icon={<FolderOpenOutlined />} loading={restoring}>
{t('settings.data.webdav.restore.button')}
</Button>
</HStack>

View File

@ -45,7 +45,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
const id = values.id.trim()
if (find(models, { id })) {
window.message.error('Model ID already exists')
window.message.error(t('error.model.exists'))
return
}

View File

@ -1,7 +1,14 @@
import { LoadingOutlined, MinusOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { Center } from '@renderer/components/Layout'
import ModelTags from '@renderer/components/ModelTags'
import { getModelLogo, isEmbeddingModel, isVisionModel, isWebSearchModel, SYSTEM_MODELS } from '@renderer/config/models'
import {
getModelLogo,
isEmbeddingModel,
isReasoningModel,
isVisionModel,
isWebSearchModel,
SYSTEM_MODELS
} from '@renderer/config/models'
import { useProvider } from '@renderer/hooks/useProvider'
import { fetchModels } from '@renderer/services/ApiService'
import { Model, Provider } from '@renderer/types'
@ -36,10 +43,16 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
const allModels = uniqBy([...systemModels, ...listModels, ...models], 'id')
const list = allModels.filter((model) => {
if (searchText && !model.id.toLocaleLowerCase().includes(searchText.toLocaleLowerCase())) {
if (
searchText &&
!model.id.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()) &&
!model.name?.toLocaleLowerCase().includes(searchText.toLocaleLowerCase())
) {
return false
}
switch (filterType) {
case 'reasoning':
return isReasoningModel(model)
case 'vision':
return isVisionModel(model)
case 'websearch':
@ -136,6 +149,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
<Center>
<Radio.Group value={filterType} onChange={(e) => setFilterType(e.target.value)} buttonStyle="solid">
<Radio.Button value="all">{t('models.all')}</Radio.Button>
<Radio.Button value="reasoning">{t('models.reasoning')}</Radio.Button>
<Radio.Button value="vision">{t('models.vision')}</Radio.Button>
<Radio.Button value="websearch">{t('models.websearch')}</Radio.Button>
<Radio.Button value="free">{t('models.free')}</Radio.Button>

View File

@ -0,0 +1,34 @@
import { useLMStudioSettings } from '@renderer/hooks/useLMStudio'
import { InputNumber } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..'
const LMStudioSettings: FC = () => {
const { keepAliveTime, setKeepAliveTime } = useLMStudioSettings()
const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime)
const { t } = useTranslation()
return (
<Container>
<SettingSubtitle style={{ marginBottom: 5 }}>{t('lmstudio.keep_alive_time.title')}</SettingSubtitle>
<InputNumber
style={{ width: '100%' }}
value={keepAliveMinutes}
onChange={(e) => setKeepAliveMinutes(Number(e))}
onBlur={() => setKeepAliveTime(keepAliveMinutes)}
suffix={t('lmstudio.keep_alive_time.placeholder')}
step={5}
/>
<SettingHelpTextRow>
<SettingHelpText>{t('lmstudio.keep_alive_time.description')}</SettingHelpText>
</SettingHelpTextRow>
</Container>
)
}
const Container = styled.div``
export default LMStudioSettings

View File

@ -42,6 +42,7 @@ import AddModelPopup from './AddModelPopup'
import ApiCheckPopup from './ApiCheckPopup'
import EditModelsPopup from './EditModelsPopup'
import GraphRAGSettings from './GraphRAGSettings'
import LMStudioSettings from './LMStudioSettings'
import OllamSettings from './OllamaSettings'
import SelectProviderModelPopup from './SelectProviderModelPopup'
@ -319,6 +320,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
</>
)}
{provider.id === 'ollama' && <OllamSettings />}
{provider.id === 'lmstudio' && <LMStudioSettings />}
{provider.id === 'graphrag-kylin-mountain' && provider.models.length > 0 && (
<GraphRAGSettings provider={provider} />
)}

View File

@ -53,7 +53,7 @@ const ProvidersList: FC = () => {
}
const getDropdownMenus = (provider: Provider): MenuProps['items'] => {
return [
const menus = [
{
label: t('common.edit'),
key: 'edit',
@ -83,6 +83,16 @@ const ProvidersList: FC = () => {
}
}
]
if (providers.filter((p) => p.id === provider.id).length > 1) {
return menus
}
if (provider.isSystem) {
return []
}
return menus
}
return (
@ -102,9 +112,7 @@ const ProvidersList: FC = () => {
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{ ...provided.draggableProps.style, marginBottom: 5 }}>
<Dropdown
menu={{ items: provider.isSystem ? [] : getDropdownMenus(provider) }}
trigger={['contextMenu']}>
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
<ProviderListItem
key={JSON.stringify(provider)}
className={provider.id === selectedProvider?.id ? 'active' : ''}

View File

@ -8,7 +8,7 @@ import { initialState, resetShortcuts, toggleShortcut, updateShortcut } from '@r
import { Shortcut } from '@renderer/types'
import { Button, Input, InputRef, Switch, Table as AntTable, Tooltip } from 'antd'
import type { ColumnsType } from 'antd/es/table'
import { FC, useRef, useState } from 'react'
import React, { FC, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -92,8 +92,32 @@ const ShortcutSettings: FC = () => {
return isMac ? '⇧' : 'Shift'
case 'CommandOrControl':
return isMac ? '⌘' : 'Ctrl'
case ' ':
return 'Space'
case 'ArrowUp':
return '↑'
case 'ArrowDown':
return '↓'
case 'ArrowLeft':
return '←'
case 'ArrowRight':
return '→'
case 'Slash':
return '/'
case 'Semicolon':
return ';'
case 'BracketLeft':
return '['
case 'BracketRight':
return ']'
case 'Backslash':
return '\\'
case 'Quote':
return "'"
case 'Comma':
return ','
case 'Minus':
return '-'
case 'Equal':
return '='
default:
return key.charAt(0).toUpperCase() + key.slice(1)
}
@ -101,6 +125,115 @@ const ShortcutSettings: FC = () => {
.join(' + ')
}
const usableEndKeys = (event: React.KeyboardEvent): string | null => {
const { code } = event
// No lock keys
// Among the commonly used keys, not including: Escape, NumpadMultiply, NumpadDivide, NumpadSubtract, NumpadAdd, NumpadDecimal
// The react-hotkeys-hook library does not differentiate between `Digit` and `Numpad`
switch (code) {
case 'KeyA':
case 'KeyB':
case 'KeyC':
case 'KeyD':
case 'KeyE':
case 'KeyF':
case 'KeyG':
case 'KeyH':
case 'KeyI':
case 'KeyJ':
case 'KeyK':
case 'KeyL':
case 'KeyM':
case 'KeyN':
case 'KeyO':
case 'KeyP':
case 'KeyQ':
case 'KeyR':
case 'KeyS':
case 'KeyT':
case 'KeyU':
case 'KeyV':
case 'KeyW':
case 'KeyX':
case 'KeyY':
case 'KeyZ':
case 'Digit0':
case 'Digit1':
case 'Digit2':
case 'Digit3':
case 'Digit4':
case 'Digit5':
case 'Digit6':
case 'Digit7':
case 'Digit8':
case 'Digit9':
case 'Numpad0':
case 'Numpad1':
case 'Numpad2':
case 'Numpad3':
case 'Numpad4':
case 'Numpad5':
case 'Numpad6':
case 'Numpad7':
case 'Numpad8':
case 'Numpad9':
return code.slice(-1)
case 'Space':
case 'Enter':
case 'Backspace':
case 'Tab':
case 'Delete':
case 'PageUp':
case 'PageDown':
case 'Insert':
case 'Home':
case 'End':
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowLeft':
case 'ArrowRight':
case 'F1':
case 'F2':
case 'F3':
case 'F4':
case 'F5':
case 'F6':
case 'F7':
case 'F8':
case 'F9':
case 'F10':
case 'F11':
case 'F12':
case 'F13':
case 'F14':
case 'F15':
case 'F16':
case 'F17':
case 'F18':
case 'F19':
return code
case 'Backquote':
return '`'
case 'Period':
return '.'
case 'NumpadEnter':
return 'Enter'
// The react-hotkeys-hook library does not handle the symbol strings for the following keys
case 'Slash':
case 'Semicolon':
case 'BracketLeft':
case 'BracketRight':
case 'Backslash':
case 'Quote':
case 'Comma':
case 'Minus':
case 'Equal':
return code
default:
return null
}
}
const handleKeyDown = (e: React.KeyboardEvent, record: Shortcut) => {
e.preventDefault()
@ -109,15 +242,9 @@ const ShortcutSettings: FC = () => {
if (e.metaKey) keys.push('Command')
if (e.altKey) keys.push('Alt')
if (e.shiftKey) keys.push('Shift')
const key = e.key
if (key.length === 1 && !['Control', 'Alt', 'Shift', 'Meta'].includes(key)) {
if (key === ' ') {
keys.push('Space')
} else {
keys.push(key.toUpperCase())
}
const endKey = usableEndKeys(e)
if (endKey) {
keys.push(endKey)
}
if (!isValidShortcut(keys)) {

View File

@ -1,4 +1,5 @@
import { REFERENCE_PROMPT } from '@renderer/config/prompts'
import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio'
import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama'
import { getKnowledgeReferences } from '@renderer/services/KnowledgeService'
import store from '@renderer/store'
@ -63,7 +64,11 @@ export default abstract class BaseProvider {
}
public get keepAliveTime() {
return this.provider.id === 'ollama' ? getOllamaKeepAliveTime() : undefined
return this.provider.id === 'ollama'
? getOllamaKeepAliveTime()
: this.provider.id === 'lmstudio'
? getLMStudioKeepAliveTime()
: undefined
}
public async fakeCompletions({ onChunk }: CompletionsParams) {
@ -85,9 +90,14 @@ export default abstract class BaseProvider {
return message.content
}
const references = await getKnowledgeReferences(base, message)
const { referencesContent, referencesCount } = await getKnowledgeReferences(base, message)
return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', references)
// 如果知识库中未检索到内容则使用通用逻辑
if (referencesCount === 0) {
return message.content
}
return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', referencesContent)
}
protected getCustomParameters(assistant: Assistant) {
@ -98,10 +108,10 @@ export default abstract class BaseProvider {
}
if (param.type === 'json') {
const value = param.value as string
return {
...acc,
[param.name]: isJSON(value) ? parseJSON(value) : value
if (value === 'undefined') {
return { ...acc, [param.name]: undefined }
}
return { ...acc, [param.name]: isJSON(value) ? parseJSON(value) : value }
}
return {
...acc,

View File

@ -42,7 +42,7 @@ export default class OpenAIProvider extends BaseProvider {
}
private get isNotSupportFiles() {
const providers = ['deepseek', 'baichuan', 'minimax']
const providers = ['deepseek', 'baichuan', 'minimax', 'doubao']
return providers.includes(this.provider.id)
}
@ -123,7 +123,9 @@ export default class OpenAIProvider extends BaseProvider {
return assistant?.settings?.temperature
}
private getProviderSpecificParameters(model: Model) {
private getProviderSpecificParameters(assistant: Assistant, model: Model) {
const { maxTokens } = getAssistantSettings(assistant)
if (this.provider.id === 'openrouter') {
if (model.id.includes('deepseek-r1')) {
return {
@ -132,6 +134,13 @@ export default class OpenAIProvider extends BaseProvider {
}
}
if (this.isOpenAIo1(model)) {
return {
max_tokens: undefined,
max_completion_tokens: maxTokens
}
}
return {}
}
@ -155,12 +164,17 @@ export default class OpenAIProvider extends BaseProvider {
return {}
}
private isOpenAIo1(model: Model) {
return model.id.startsWith('o1')
}
async completions({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams): Promise<void> {
const defaultModel = getDefaultModel()
const model = assistant.model || defaultModel
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
if (['o1', 'o1-2024-12-17'].includes(model.id) || model.id.startsWith('o3')) {
systemMessage = {
role: 'developer',
@ -183,7 +197,7 @@ export default class OpenAIProvider extends BaseProvider {
userMessages.push(await this.getMessageParam(message, model))
}
const isOpenAIo1 = model.id.startsWith('o1')
const isOpenAIo1 = this.isOpenAIo1(model)
const isSupportStreamOutput = () => {
if (isOpenAIo1) {
@ -192,6 +206,10 @@ export default class OpenAIProvider extends BaseProvider {
return streamOutput
}
let hasReasoningContent = false
const isReasoningJustDone = (delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta) =>
hasReasoningContent ? !!delta?.content : delta?.content === '</think>'
let time_first_token_millsec = 0
let time_first_content_millsec = 0
const start_time_millsec = new Date().getTime()
@ -207,7 +225,7 @@ export default class OpenAIProvider extends BaseProvider {
stream: isSupportStreamOutput(),
...this.getReasoningEffort(assistant, model),
...getOpenAIWebSearchParams(assistant, model),
...this.getProviderSpecificParameters(model),
...this.getProviderSpecificParameters(assistant, model),
...this.getCustomParameters(assistant)
})
@ -229,20 +247,24 @@ export default class OpenAIProvider extends BaseProvider {
break
}
const delta = chunk.choices[0]?.delta
// @ts-expect-error `reasoning_content` not supported by OpenAI for now
if (delta?.reasoning_content) {
hasReasoningContent = true
}
if (time_first_token_millsec == 0) {
time_first_token_millsec = new Date().getTime() - start_time_millsec
}
//修复逻辑判断当content为</think>时time_first_content_millsec才会被赋值原有代码无意义.
if (time_first_content_millsec == 0 && chunk.choices[0]?.delta?.content == '</think>') {
if (time_first_content_millsec == 0 && isReasoningJustDone(delta)) {
time_first_content_millsec = new Date().getTime()
}
const time_completion_millsec = new Date().getTime() - start_time_millsec
const time_thinking_millsec = time_first_content_millsec ? time_first_content_millsec - start_time_millsec : 0
const delta = chunk.choices[0]?.delta
// Extract citations from the raw response if available
const citations = (chunk as OpenAI.Chat.Completions.ChatCompletionChunk & { citations?: string[] })?.citations
@ -265,12 +287,14 @@ export default class OpenAIProvider extends BaseProvider {
async translate(message: Message, assistant: Assistant, onResponse?: (text: string) => void) {
const defaultModel = getDefaultModel()
const model = assistant.model || defaultModel
const messages = [
{ role: 'system', content: assistant.prompt },
{ role: 'user', content: message.content }
]
const messages = message.content
? [
{ role: 'system', content: assistant.prompt },
{ role: 'user', content: message.content }
]
: [{ role: 'user', content: assistant.prompt }]
const isOpenAIo1 = model.id.startsWith('o1')
const isOpenAIo1 = this.isOpenAIo1(model)
const isSupportedStreamOutput = () => {
if (!onResponse) {
@ -344,7 +368,7 @@ export default class OpenAIProvider extends BaseProvider {
// 针对思考类模型的返回,总结仅截取</think>之后的内容
let content = response.choices[0].message?.content || ''
content = content.replace(/^<think>(.*?)<\/think>/s, '')
return removeSpecialCharacters(content.substring(0, 50))
}
@ -393,7 +417,6 @@ export default class OpenAIProvider extends BaseProvider {
const body = {
model: model.id,
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 100,
stream: false
}

View File

@ -1,9 +1,9 @@
import type { AddLoaderReturn } from '@llm-tools/embedjs-interfaces'
import db from '@renderer/databases'
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
import store from '@renderer/store'
import { clearCompletedProcessing, updateBaseItemUniqueId, updateItemProcessingStatus } from '@renderer/store/knowledge'
import { KnowledgeItem } from '@renderer/types'
import type { LoaderReturn } from '@shared/config/types'
class KnowledgeQueue {
private processing: Map<string, boolean> = new Map()
@ -113,7 +113,7 @@ class KnowledgeQueue {
throw new Error(`[KnowledgeQueue] Source item ${item.id} not found in base ${baseId}`)
}
let result: AddLoaderReturn | null = null
let result: LoaderReturn | null = null
let note, content
console.log(`[KnowledgeQueue] Processing item: ${sourceItem.content}`)
@ -146,16 +146,16 @@ class KnowledgeQueue {
updateBaseItemUniqueId({
baseId,
itemId: item.id,
uniqueId: result.uniqueId
uniqueId: result.uniqueId,
uniqueIds: result.uniqueIds
})
)
}
console.debug(`[KnowledgeQueue] Updated uniqueId for item ${item.id} in base ${baseId}`)
console.debug(`[KnowledgeQueue] Updated uniqueId for item ${item.id} in base ${baseId} `)
setTimeout(() => store.dispatch(clearCompletedProcessing({ baseId })), 1000)
} catch (error) {
console.error(`[KnowledgeQueue] Error processing item ${item.id}:`, error)
console.error(`[KnowledgeQueue] Error processing item ${item.id}: `, error)
store.dispatch(
updateItemProcessingStatus({
baseId,

View File

@ -214,12 +214,12 @@ export async function checkApi(provider: Provider, model: Model) {
const key = 'api-check'
const style = { marginTop: '3vh' }
if (provider.id !== 'ollama') {
if (provider.id !== 'ollama' && provider.id !== 'lmstudio') {
if (!provider.apiKey) {
window.message.error({ content: i18n.t('message.error.enter.api.key'), key, style })
return {
valid: false,
error: new Error('message.error.enter.api.key')
error: new Error(i18n.t('message.error.enter.api.key'))
}
}
}
@ -252,7 +252,7 @@ export async function checkApi(provider: Provider, model: Model) {
function hasApiKey(provider: Provider) {
if (!provider) return false
if (provider.id === 'ollama') return true
if (provider.id === 'ollama' || provider.id === 'lmstudio') return true
return !isEmpty(provider.apiKey)
}

View File

@ -106,7 +106,6 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings =>
streamOutput: assistant?.settings?.streamOutput ?? true,
hideMessages: assistant?.settings?.hideMessages ?? false,
defaultModel: assistant?.defaultModel ?? undefined,
autoResetModel: assistant?.settings?.autoResetModel ?? false,
customParameters: assistant?.settings?.customParameters ?? []
}
}

View File

@ -22,5 +22,6 @@ export const EVENT_NAMES = {
EXPORT_TOPIC_IMAGE: 'EXPORT_TOPIC_IMAGE',
LOCATE_MESSAGE: 'LOCATE_MESSAGE',
ADD_NEW_TOPIC: 'ADD_NEW_TOPIC',
RESEND_MESSAGE: 'RESEND_MESSAGE'
RESEND_MESSAGE: 'RESEND_MESSAGE',
SHOW_MODEL_SELECTOR: 'SHOW_MODEL_SELECTOR'
}

View File

@ -1,8 +1,9 @@
import type { ExtractChunkData } from '@llm-tools/embedjs-interfaces'
import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT } from '@renderer/config/constant'
import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant'
import { getEmbeddingMaxContext } from '@renderer/config/embedings'
import AiProvider from '@renderer/providers/AiProvider'
import { FileType, KnowledgeBase, KnowledgeBaseParams, Message } from '@renderer/types'
import { t } from 'i18next'
import { take } from 'lodash'
import { getProviderByModel } from './AssistantService'
@ -79,10 +80,25 @@ export const getKnowledgeSourceUrl = async (item: ExtractChunkData & { file: Fil
}
export const getKnowledgeReferences = async (base: KnowledgeBase, message: Message) => {
const searchResults = await window.api.knowledgeBase.search({
search: message.content,
base: getKnowledgeBaseParams(base)
})
const searchResults = await window.api.knowledgeBase
.search({
search: message.content,
base: getKnowledgeBaseParams(base)
})
.then((results) =>
results.filter((item) => {
const threshold = base.threshold || DEFAULT_KNOWLEDGE_THRESHOLD
return item.score >= threshold
})
)
if (searchResults.length === 0) {
window.message.info({
content: t('knowledge.no_match'),
duration: 4,
key: 'knowledge-base-no-match-info'
})
return { referencesContent: '', referencesCount: 0 }
}
const _searchResults = await Promise.all(
searchResults.map(async (item) => {
@ -107,5 +123,5 @@ export const getKnowledgeReferences = async (base: KnowledgeBase, message: Messa
const referencesContent = `\`\`\`json\n${JSON.stringify(references, null, 2)}\n\`\`\``
return referencesContent
return { referencesContent, referencesCount: references.length }
}

View File

@ -41,8 +41,7 @@ const assistantsSlice = createSlice({
enableMaxTokens: false,
maxTokens: 0,
streamOutput: true,
hideMessages: false,
autoResetModel: false
hideMessages: false
}
}
agent.settings[key] = settings[key]

Some files were not shown because too many files have changed in this diff Show More