Merge branch 'main' into local-pr-3734
This commit is contained in:
+5
-5
@@ -1,8 +1,8 @@
|
||||
# provider: generic
|
||||
# url: http://127.0.0.1:8080
|
||||
# updaterCacheDirName: cherry-studio-updater
|
||||
# provider: github
|
||||
# repo: cherry-studio
|
||||
# owner: kangfenmao
|
||||
provider: generic
|
||||
url: https://cherrystudio.ocool.online
|
||||
provider: github
|
||||
repo: cherry-studio
|
||||
owner: kangfenmao
|
||||
# provider: generic
|
||||
# url: https://cherrystudio.ocool.online
|
||||
|
||||
+12
-13
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -50,12 +50,8 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/exa": "^7.3.3",
|
||||
"@agentic/searxng": "^7.3.3",
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@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",
|
||||
@@ -68,12 +64,7 @@
|
||||
"@llm-tools/embedjs-loader-xml": "^0.1.28",
|
||||
"@llm-tools/embedjs-openai": "^0.1.28",
|
||||
"@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/pdf-parse": "^1.1.4",
|
||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"apache-arrow": "^18.1.0",
|
||||
"docx": "^9.0.2",
|
||||
"electron-log": "^5.1.5",
|
||||
"electron-store": "^8.2.0",
|
||||
@@ -83,30 +74,34 @@
|
||||
"fetch-socks": "^1.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"officeparser": "^4.1.1",
|
||||
"p-queue": "^8.1.0",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"proxy-agent": "^6.5.0",
|
||||
"tar": "^7.4.3",
|
||||
"tokenx": "^0.4.1",
|
||||
"undici": "^7.4.0",
|
||||
"webdav": "^5.8.0",
|
||||
"zipread": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/exa": "^7.3.3",
|
||||
"@agentic/searxng": "^7.3.3",
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@anthropic-ai/sdk": "^0.38.0",
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@eslint-react/eslint-plugin": "^1.36.1",
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@google/genai": "^0.4.0",
|
||||
"@hello-pangea/dnd": "^16.6.0",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@llm-tools/embedjs-loader-image": "^0.1.28",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
"@tavily/core": "patch:@tavily/core@npm%3A0.3.1#~/.yarn/patches/@tavily-core-npm-0.3.1-fe69bf2bea.patch",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/adm-zip": "^0",
|
||||
"@types/fs-extra": "^11",
|
||||
"@types/lodash": "^4.17.5",
|
||||
@@ -118,6 +113,7 @@
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/pdf-parse": "^1.1.4",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"antd": "^5.22.5",
|
||||
"applescript": "^1.0.0",
|
||||
@@ -145,7 +141,9 @@
|
||||
"lint-staged": "^15.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mime": "^4.0.4",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"openai": "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch",
|
||||
"p-queue": "^8.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -170,6 +168,7 @@
|
||||
"string-width": "^7.2.0",
|
||||
"styled-components": "^6.1.11",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tokenx": "^0.4.1",
|
||||
"typescript": "^5.6.2",
|
||||
"uuid": "^10.0.0",
|
||||
"vite": "^5.0.12"
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* OCOOL_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts
|
||||
*/
|
||||
|
||||
// OCOOL API KEY
|
||||
const OCOOL_API_KEY = process.env.OCOOL_API_KEY
|
||||
|
||||
const INDEX = [
|
||||
// 语言的名称 代码 用来翻译的模型
|
||||
{ name: 'France', code: 'fr-fr', model: 'qwen2.5-32b-instruct' },
|
||||
{ name: 'Spanish', code: 'es-es', model: 'qwen2.5-32b-instruct' },
|
||||
{ name: 'Portuguese', code: 'pt-pt', model: 'qwen2.5-72b-instruct' },
|
||||
{ name: 'Greek', code: 'el-gr', model: 'qwen-turbo' }
|
||||
]
|
||||
|
||||
const fs = require('fs')
|
||||
import OpenAI from 'openai'
|
||||
|
||||
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as object
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: OCOOL_API_KEY,
|
||||
baseURL: 'https://one.ocoolai.com/v1'
|
||||
})
|
||||
|
||||
// 递归遍历翻译
|
||||
async function translate(zh: object, obj: object, target: string, model: string, updateFile) {
|
||||
const texts: { [key: string]: string } = {}
|
||||
for (const e in zh) {
|
||||
if (typeof zh[e] == 'object') {
|
||||
// 遍历下一层
|
||||
if (!obj[e] || typeof obj[e] != 'object') obj[e] = {}
|
||||
await translate(zh[e], obj[e], target, model, updateFile)
|
||||
} else {
|
||||
// 加入到本层待翻译列表
|
||||
if (!obj[e] || typeof obj[e] != 'string') {
|
||||
texts[e] = zh[e]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(texts).length > 0) {
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: model,
|
||||
response_format: { type: 'json_object' },
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `
|
||||
You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on Russian language corpora, you are proficient in using the Russian language.
|
||||
Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the Russian language.
|
||||
When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted.
|
||||
Output in JSON.
|
||||
######################################################
|
||||
INPUT
|
||||
######################################################
|
||||
${JSON.stringify({
|
||||
confirm: '确定要备份数据吗?',
|
||||
select_model: '选择模型',
|
||||
title: '文件',
|
||||
deeply_thought: '已深度思考(用时 {{secounds}} 秒)'
|
||||
})}
|
||||
######################################################
|
||||
MAKE SURE TO OUTPUT IN Russian. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
######################################################
|
||||
`
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: JSON.stringify({
|
||||
confirm: 'Подтвердите резервное копирование данных?',
|
||||
select_model: 'Выберите Модель',
|
||||
title: 'Файл',
|
||||
deeply_thought: 'Глубоко продумано (заняло {{seconds}} секунд)'
|
||||
})
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `
|
||||
You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on ${target} language corpora, you are proficient in using the ${target} language.
|
||||
Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the ${target} language.
|
||||
When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted.
|
||||
Output in JSON.
|
||||
######################################################
|
||||
INPUT
|
||||
######################################################
|
||||
${JSON.stringify(texts)}
|
||||
######################################################
|
||||
MAKE SURE TO OUTPUT IN ${target}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
######################################################
|
||||
`
|
||||
}
|
||||
]
|
||||
})
|
||||
// 添加翻译后的键值,并打印错译漏译内容
|
||||
try {
|
||||
const result = JSON.parse(completion.choices[0].message.content!)
|
||||
for (const e in texts) {
|
||||
if (result[e] && typeof result[e] === 'string') {
|
||||
obj[e] = result[e]
|
||||
} else {
|
||||
console.log('[warning]', `missing value "${e}" in ${target} translation`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[error]', e)
|
||||
for (const e in texts) {
|
||||
console.log('[warning]', `missing value "${e}" in ${target} translation`)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除多余的键值
|
||||
for (const e in obj) {
|
||||
if (!zh[e]) {
|
||||
delete obj[e]
|
||||
}
|
||||
}
|
||||
// 更新文件
|
||||
updateFile()
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
for (const { name, code, model } of INDEX) {
|
||||
const obj = fs.existsSync(`src/renderer/src/i18n/translate/${code}.json`)
|
||||
? JSON.parse(fs.readFileSync(`src/renderer/src/i18n/translate/${code}.json`, 'utf8'))
|
||||
: {}
|
||||
await translate(zh, obj, name, model, () => {
|
||||
fs.writeFileSync(`src/renderer/src/i18n/translate/${code}.json`, JSON.stringify(obj, null, 2), 'utf8')
|
||||
})
|
||||
}
|
||||
})()
|
||||
@@ -4,6 +4,7 @@ import { app, ipcMain } from 'electron'
|
||||
import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'
|
||||
|
||||
import { registerIpc } from './ipc'
|
||||
import { configManager } from './services/ConfigManager'
|
||||
import { registerShortcuts } from './services/ShortcutService'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
@@ -21,6 +22,12 @@ if (!app.requestSingleInstanceLock()) {
|
||||
// Set app user model id for windows
|
||||
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
||||
|
||||
// Mac: Hide dock icon before window creation when launch to tray is set
|
||||
const isLaunchToTray = configManager.getLaunchToTray()
|
||||
if (isLaunchToTray) {
|
||||
app.dock?.hide()
|
||||
}
|
||||
|
||||
const mainWindow = windowService.createMainWindow()
|
||||
new TrayService()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { isMac, isWin } from '@main/constant'
|
||||
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
||||
import { MCPServer, Shortcut, ThemeMode } from '@types'
|
||||
import { BrowserWindow, ipcMain, session, shell } from 'electron'
|
||||
@@ -68,11 +69,30 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
configManager.setLanguage(language)
|
||||
})
|
||||
|
||||
// launch on boot
|
||||
ipcMain.handle('app:set-launch-on-boot', (_, openAtLogin: boolean) => {
|
||||
// Set login item settings for windows and mac
|
||||
// linux is not supported because it requires more file operations
|
||||
if (isWin || isMac) {
|
||||
app.setLoginItemSettings({ openAtLogin })
|
||||
}
|
||||
})
|
||||
|
||||
// launch to tray
|
||||
ipcMain.handle('app:set-launch-to-tray', (_, isActive: boolean) => {
|
||||
configManager.setLaunchToTray(isActive)
|
||||
})
|
||||
|
||||
// tray
|
||||
ipcMain.handle('app:set-tray', (_, isActive: boolean) => {
|
||||
configManager.setTray(isActive)
|
||||
})
|
||||
|
||||
// to tray on close
|
||||
ipcMain.handle('app:set-tray-on-close', (_, isActive: boolean) => {
|
||||
configManager.setTrayOnClose(isActive)
|
||||
})
|
||||
|
||||
ipcMain.handle('app:restart-tray', () => TrayService.getInstance().restartTray())
|
||||
|
||||
ipcMain.handle('config:set', (_, key: string, value: any) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ export default class DefaultReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
|
||||
async rerank(): Promise<ExtractChunkData[]> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
@@ -10,9 +10,15 @@ export default class JinaReranker extends BaseReranker {
|
||||
}
|
||||
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const baseURL = this.base?.rerankBaseURL?.endsWith('/')
|
||||
let baseURL = this.base?.rerankBaseURL?.endsWith('/')
|
||||
? this.base.rerankBaseURL.slice(0, -1)
|
||||
: this.base.rerankBaseURL
|
||||
|
||||
// 必须携带/v1,否则会404
|
||||
if (baseURL && !baseURL.endsWith('/v1')) {
|
||||
baseURL = `${baseURL}/v1`
|
||||
}
|
||||
|
||||
const url = `${baseURL}/rerank`
|
||||
|
||||
const requestBody = {
|
||||
@@ -40,9 +46,9 @@ export default class JinaReranker extends BaseReranker {
|
||||
})
|
||||
.filter((doc): doc is ExtractChunkData => doc !== undefined)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
} catch (error) {
|
||||
console.error('Jina Reranker API 错误:', error)
|
||||
throw error
|
||||
} catch (error: any) {
|
||||
console.error('Jina Reranker API 错误:', error.status)
|
||||
throw new Error(`${error} - BaseUrl: ${baseURL}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,15 @@ export default class SiliconFlowReranker extends BaseReranker {
|
||||
}
|
||||
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const baseURL = this.base?.rerankBaseURL?.endsWith('/')
|
||||
let baseURL = this.base?.rerankBaseURL?.endsWith('/')
|
||||
? this.base.rerankBaseURL.slice(0, -1)
|
||||
: this.base.rerankBaseURL
|
||||
|
||||
// 必须携带/v1,否则会404
|
||||
if (baseURL && !baseURL.endsWith('/v1')) {
|
||||
baseURL = `${baseURL}/v1`
|
||||
}
|
||||
|
||||
const url = `${baseURL}/rerank`
|
||||
|
||||
const requestBody = {
|
||||
@@ -42,9 +48,9 @@ export default class SiliconFlowReranker extends BaseReranker {
|
||||
})
|
||||
.filter((doc): doc is ExtractChunkData => doc !== undefined)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
} catch (error) {
|
||||
console.error('SiliconFlow Reranker API 错误:', error)
|
||||
throw error
|
||||
} catch (error: any) {
|
||||
console.error('SiliconFlow Reranker API 错误:', error.status)
|
||||
throw new Error(`${error} - BaseUrl: ${baseURL}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@ export class ConfigManager {
|
||||
this.store.set('theme', theme)
|
||||
}
|
||||
|
||||
getLaunchToTray(): boolean {
|
||||
return !!this.store.get('launchToTray', false)
|
||||
}
|
||||
|
||||
setLaunchToTray(value: boolean) {
|
||||
this.store.set('launchToTray', value)
|
||||
}
|
||||
|
||||
getTray(): boolean {
|
||||
return !!this.store.get('tray', true)
|
||||
}
|
||||
@@ -39,6 +47,14 @@ export class ConfigManager {
|
||||
this.notifySubscribers('tray', value)
|
||||
}
|
||||
|
||||
getTrayOnClose(): boolean {
|
||||
return !!this.store.get('trayOnClose', true)
|
||||
}
|
||||
|
||||
setTrayOnClose(value: boolean) {
|
||||
this.store.set('trayOnClose', value)
|
||||
}
|
||||
|
||||
getZoomFactor(): number {
|
||||
return this.store.get('zoomFactor', 1) as number
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ export class ProxyManager {
|
||||
await this.setSessionsProxy({ mode: 'system' })
|
||||
const proxyString = await session.defaultSession.resolveProxy('https://dummy.com')
|
||||
const [protocol, address] = proxyString.split(';')[0].split(' ')
|
||||
console.log('protocol', protocol)
|
||||
const url = protocol === 'PROXY' ? `http://${address}` : null
|
||||
if (url && url !== this.config.url) {
|
||||
this.config.url = url.toLowerCase()
|
||||
|
||||
@@ -115,7 +115,20 @@ const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutForm
|
||||
}
|
||||
|
||||
export function registerShortcuts(window: BrowserWindow) {
|
||||
const register = () => {
|
||||
window.once('ready-to-show', () => {
|
||||
if (configManager.getLaunchToTray()) {
|
||||
registerOnlyUniversalShortcuts()
|
||||
}
|
||||
})
|
||||
|
||||
//only for clearer code
|
||||
const registerOnlyUniversalShortcuts = () => {
|
||||
register(true)
|
||||
}
|
||||
|
||||
//onlyUniversalShortcuts is used to register shortcuts that are not window specific, like show_app & mini_window
|
||||
//onlyUniversalShortcuts is needed when we launch to tray
|
||||
const register = (onlyUniversalShortcuts: boolean = false) => {
|
||||
if (window.isDestroyed()) return
|
||||
|
||||
const shortcuts = configManager.getShortcuts()
|
||||
@@ -132,6 +145,11 @@ export function registerShortcuts(window: BrowserWindow) {
|
||||
return
|
||||
}
|
||||
|
||||
// only register universal shortcuts when needed
|
||||
if (onlyUniversalShortcuts && !['show_app', 'mini_window'].includes(shortcut.key)) {
|
||||
return
|
||||
}
|
||||
|
||||
const handler = getShortcutHandler(shortcut)
|
||||
if (!handler) {
|
||||
return
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { is } from '@electron-toolkit/utils'
|
||||
import { isDev, isLinux, isWin } from '@main/constant'
|
||||
import { isDev, isLinux, isMac, isWin } from '@main/constant'
|
||||
import { getFilesDir } from '@main/utils/file'
|
||||
import { app, BrowserWindow, ipcMain, Menu, MenuItem, shell } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
@@ -39,8 +39,6 @@ export class WindowService {
|
||||
})
|
||||
|
||||
const theme = configManager.getTheme()
|
||||
const isMac = process.platform === 'darwin'
|
||||
const isLinux = process.platform === 'linux'
|
||||
|
||||
this.mainWindow = new BrowserWindow({
|
||||
x: mainWindowState.x,
|
||||
@@ -146,7 +144,12 @@ export class WindowService {
|
||||
private setupWindowEvents(mainWindow: BrowserWindow) {
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
|
||||
mainWindow.show()
|
||||
|
||||
// show window only when laucn to tray not set
|
||||
const isLaunchToTray = configManager.getLaunchToTray()
|
||||
if (!isLaunchToTray) {
|
||||
mainWindow.show()
|
||||
}
|
||||
})
|
||||
|
||||
// 处理全屏相关事件
|
||||
@@ -255,12 +258,20 @@ export class WindowService {
|
||||
return app.quit()
|
||||
}
|
||||
|
||||
// 没有开启托盘,且是Windows或Linux系统,直接退出
|
||||
const notInTray = !configManager.getTray()
|
||||
if ((isWin || isLinux) && notInTray) {
|
||||
return app.quit()
|
||||
// 托盘及关闭行为设置
|
||||
const isShowTray = configManager.getTray()
|
||||
const isTrayOnClose = configManager.getTrayOnClose()
|
||||
|
||||
// 没有开启托盘,或者开启了托盘,但设置了直接关闭,应执行直接退出
|
||||
if (!isShowTray || (isShowTray && !isTrayOnClose)) {
|
||||
// 如果是Windows或Linux,直接退出
|
||||
// mac按照系统默认行为,不退出
|
||||
if (isWin || isLinux) {
|
||||
return app.quit()
|
||||
}
|
||||
}
|
||||
|
||||
//上述逻辑以下,是“开启托盘+设置关闭时最小化到托盘”的情况
|
||||
// 如果是Windows或Linux,且处于全屏状态,则退出应用
|
||||
if (this.wasFullScreen) {
|
||||
if (isWin || isLinux) {
|
||||
@@ -271,8 +282,13 @@ export class WindowService {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
mainWindow.hide()
|
||||
|
||||
if (isMac && isTrayOnClose) {
|
||||
app.dock?.hide() //for mac to hide to tray
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
@@ -301,6 +317,9 @@ export class WindowService {
|
||||
this.mainWindow = this.createMainWindow()
|
||||
this.mainWindow.focus()
|
||||
}
|
||||
|
||||
//for mac users, when window is shown, should show dock icon (dock may be set to hide when launch)
|
||||
app.dock?.show()
|
||||
}
|
||||
|
||||
public showMiniWindow() {
|
||||
@@ -310,9 +329,6 @@ export class WindowService {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
this.mainWindow.hide()
|
||||
}
|
||||
if (this.selectionMenuWindow && !this.selectionMenuWindow.isDestroyed()) {
|
||||
this.selectionMenuWindow.hide()
|
||||
}
|
||||
@@ -327,8 +343,6 @@ export class WindowService {
|
||||
return
|
||||
}
|
||||
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
this.miniWindow = new BrowserWindow({
|
||||
width: 500,
|
||||
height: 520,
|
||||
@@ -403,7 +417,6 @@ export class WindowService {
|
||||
}
|
||||
|
||||
const theme = configManager.getTheme()
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
this.selectionMenuWindow = new BrowserWindow({
|
||||
width: 280,
|
||||
|
||||
Vendored
+3
@@ -23,7 +23,10 @@ declare global {
|
||||
openWebsite: (url: string) => void
|
||||
setProxy: (proxy: string | undefined) => void
|
||||
setLanguage: (theme: LanguageVarious) => void
|
||||
setLaunchOnBoot: (isActive: boolean) => void
|
||||
setLaunchToTray: (isActive: boolean) => void
|
||||
setTray: (isActive: boolean) => void
|
||||
setTrayOnClose: (isActive: boolean) => void
|
||||
restartTray: () => void
|
||||
setTheme: (theme: 'light' | 'dark') => void
|
||||
minApp: (options: { url: string; windowOptions?: Electron.BrowserWindowConstructorOptions }) => void
|
||||
|
||||
@@ -11,7 +11,10 @@ const api = {
|
||||
checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'),
|
||||
showUpdateDialog: () => ipcRenderer.invoke('app:show-update-dialog'),
|
||||
setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang),
|
||||
setLaunchOnBoot: (isActive: boolean) => ipcRenderer.invoke('app:set-launch-on-boot', isActive),
|
||||
setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke('app:set-launch-to-tray', isActive),
|
||||
setTray: (isActive: boolean) => ipcRenderer.invoke('app:set-tray', isActive),
|
||||
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke('app:set-tray-on-close', isActive),
|
||||
restartTray: () => ipcRenderer.invoke('app:restart-tray'),
|
||||
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke('app:set-theme', theme),
|
||||
openWebsite: (url: string) => ipcRenderer.invoke('open:website', url),
|
||||
|
||||
@@ -8,7 +8,7 @@ const ReasoningIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={t('models.reasoning')} placement="top">
|
||||
<Tooltip title={t('models.type.reasoning')} placement="top">
|
||||
<Icon className="iconfont icon-thinking" {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
|
||||
@@ -9,7 +9,7 @@ const VisionIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>,
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={t('models.vision')} placement="top">
|
||||
<Tooltip title={t('models.type.vision')} placement="top">
|
||||
<Icon {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
|
||||
@@ -9,7 +9,7 @@ const WebSearchIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={t('models.websearch')} placement="top">
|
||||
<Tooltip title={t('models.type.websearch')} placement="top">
|
||||
<Icon {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
isEmbeddingModel,
|
||||
isFunctionCallingModel,
|
||||
isReasoningModel,
|
||||
isRerankModel,
|
||||
isVisionModel,
|
||||
isWebSearchModel
|
||||
} from '@renderer/config/models'
|
||||
@@ -32,8 +33,9 @@ const ModelTags: FC<ModelTagsProps> = ({ model, showFree = true, showReasoning =
|
||||
{isWebSearchModel(model) && <WebSearchIcon />}
|
||||
{showReasoning && isReasoningModel(model) && <ReasoningIcon />}
|
||||
{showToolsCalling && isFunctionCallingModel(model) && <ToolsCallingIcon />}
|
||||
{isEmbeddingModel(model) && <Tag color="orange">{t('models.embedding')}</Tag>}
|
||||
{showFree && isFreeModel(model) && <Tag color="green">{t('models.free')}</Tag>}
|
||||
{isEmbeddingModel(model) && <Tag color="orange">{t('models.type.embedding')}</Tag>}
|
||||
{showFree && isFreeModel(model) && <Tag color="green">{t('models.type.free')}</Tag>}
|
||||
{isRerankModel(model) && <Tag color="geekblue">{t('models.type.rerank')}</Tag>}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import { exportMarkdownToObsidian } from '@renderer/utils/export'
|
||||
import { Form, Input, Modal, Select } from 'antd'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
interface ObsidianExportDialogProps {
|
||||
title: string
|
||||
markdown: string
|
||||
open: boolean // 使用 open 属性替代 visible
|
||||
onClose: (success: boolean) => void
|
||||
obsidianTags: string | null
|
||||
processingMethod: string | '3' //默认新增(存在就覆盖)
|
||||
}
|
||||
|
||||
const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
||||
title,
|
||||
markdown,
|
||||
obsidianTags,
|
||||
processingMethod,
|
||||
open,
|
||||
onClose
|
||||
}) => {
|
||||
const [state, setState] = useState({
|
||||
title: title,
|
||||
tags: obsidianTags || '',
|
||||
createdAt: new Date().toISOString().split('T')[0],
|
||||
source: 'Cherry Studio',
|
||||
processingMethod: processingMethod
|
||||
})
|
||||
|
||||
const handleOk = async () => {
|
||||
//构建content 并复制到粘贴板
|
||||
let content = ''
|
||||
if (state.processingMethod !== '3') {
|
||||
content = `\n---\n${markdown}`
|
||||
} else {
|
||||
content = `---
|
||||
\ntitle: ${state.title}
|
||||
\ncreated: ${state.createdAt}
|
||||
\nsource: ${state.source}
|
||||
\ntags: ${state.tags}
|
||||
\n---\n${markdown}`
|
||||
}
|
||||
if (content === '') {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
|
||||
}
|
||||
await navigator.clipboard.writeText(content)
|
||||
markdown = ''
|
||||
exportMarkdownToObsidian(state)
|
||||
onClose(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
onClose(false)
|
||||
}
|
||||
|
||||
const handleChange = (key: string, value: any) => {
|
||||
setState((prevState) => ({ ...prevState, [key]: value }))
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={i18n.t('chat.topics.export.obsidian_atributes')}
|
||||
open={open} // 使用 open 属性
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
closable
|
||||
maskClosable
|
||||
centered
|
||||
okButtonProps={{ type: 'primary' }}
|
||||
okText={i18n.t('chat.topics.export.obsidian_btn')}>
|
||||
<Form layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} labelAlign="left">
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_title')}>
|
||||
<Input
|
||||
value={state.title}
|
||||
onChange={(e) => handleChange('title', e.target.value)}
|
||||
placeholder={i18n.t('chat.topics.export.obsidian_title_placeholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_tags')}>
|
||||
<Input
|
||||
value={state.tags}
|
||||
onChange={(e) => handleChange('tags', e.target.value)}
|
||||
placeholder={i18n.t('chat.topics.export.obsidian_tags_placeholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_created')}>
|
||||
<Input
|
||||
value={state.createdAt}
|
||||
onChange={(e) => handleChange('createdAt', e.target.value)}
|
||||
placeholder={i18n.t('chat.topics.export.obsidian_created_placeholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_source')}>
|
||||
<Input
|
||||
value={state.source}
|
||||
onChange={(e) => handleChange('source', e.target.value)}
|
||||
placeholder={i18n.t('chat.topics.export.obsidian_source_placeholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_operate')}>
|
||||
<Select
|
||||
value={state.processingMethod}
|
||||
onChange={(value) => handleChange('processingMethod', value)}
|
||||
placeholder={i18n.t('chat.topics.export.obsidian_operate_placeholder')}
|
||||
allowClear>
|
||||
<Option value="1">{i18n.t('chat.topics.export.obsidian_operate_append')}</Option>
|
||||
<Option value="2">{i18n.t('chat.topics.export.obsidian_operate_prepend')}</Option>
|
||||
<Option value="3">{i18n.t('chat.topics.export.obsidian_operate_new_or_overwrite')}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ObsidianExportDialog
|
||||
@@ -1,228 +0,0 @@
|
||||
import { FileOutlined, FolderOutlined } from '@ant-design/icons'
|
||||
import { Spin, Switch, Tree } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
defaultPath: string
|
||||
obsidianUrl: string
|
||||
obsidianApiKey: string
|
||||
onPathChange: (path: string, isMdFile: boolean) => void
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
title: string
|
||||
key: string
|
||||
isLeaf: boolean
|
||||
isMdFile?: boolean
|
||||
children?: TreeNode[]
|
||||
}
|
||||
|
||||
const ObsidianFolderSelector: FC<Props> = ({ defaultPath, obsidianUrl, obsidianApiKey, onPathChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const [treeData, setTreeData] = useState<TreeNode[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>(['/'])
|
||||
const [showMdFiles, setShowMdFiles] = useState<boolean>(false)
|
||||
// 当前选中的节点信息
|
||||
const [currentSelection, setCurrentSelection] = useState({
|
||||
path: defaultPath,
|
||||
isMdFile: false
|
||||
})
|
||||
// 使用key强制Tree组件重新渲染
|
||||
const [treeKey, setTreeKey] = useState<number>(0)
|
||||
|
||||
// 只初始化根节点,不立即加载内容
|
||||
useEffect(() => {
|
||||
initializeRootNode()
|
||||
}, [showMdFiles])
|
||||
|
||||
// 初始化根节点,但不自动加载子节点
|
||||
const initializeRootNode = () => {
|
||||
const rootNode: TreeNode = {
|
||||
title: '/',
|
||||
key: '/',
|
||||
isLeaf: false
|
||||
}
|
||||
|
||||
setTreeData([rootNode])
|
||||
}
|
||||
|
||||
// 异步加载子节点
|
||||
const loadData = async (node: any) => {
|
||||
if (node.isLeaf) return // 如果是叶子节点(md文件),不加载子节点
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
// 确保路径末尾有斜杠
|
||||
const path = node.key === '/' ? '' : node.key
|
||||
const requestPath = path.endsWith('/') ? path : `${path}/`
|
||||
|
||||
const response = await fetch(`${obsidianUrl}vault${requestPath}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${obsidianApiKey}`
|
||||
}
|
||||
})
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok || (!data?.files && data?.errorCode !== 40400)) {
|
||||
throw new Error('获取文件夹失败')
|
||||
}
|
||||
|
||||
const childNodes: TreeNode[] = (data.files || [])
|
||||
.filter((file: string) => file.endsWith('/') || (showMdFiles && file.endsWith('.md'))) // 根据开关状态决定是否显示md文件
|
||||
.map((file: string) => {
|
||||
// 修复路径问题,避免重复的斜杠
|
||||
const normalizedFile = file.replace('/', '')
|
||||
const isMdFile = file.endsWith('.md')
|
||||
const childPath = requestPath.endsWith('/')
|
||||
? `${requestPath}${normalizedFile}${isMdFile ? '' : '/'}`
|
||||
: `${requestPath}/${normalizedFile}${isMdFile ? '' : '/'}`
|
||||
|
||||
return {
|
||||
title: normalizedFile,
|
||||
key: childPath,
|
||||
isLeaf: isMdFile,
|
||||
isMdFile
|
||||
}
|
||||
})
|
||||
|
||||
// 更新节点的子节点
|
||||
setTreeData((origin) => {
|
||||
const loop = (data: TreeNode[], key: string, children: TreeNode[]): TreeNode[] => {
|
||||
return data.map((item) => {
|
||||
if (item.key === key) {
|
||||
return {
|
||||
...item,
|
||||
children
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
children: loop(item.children, key, children)
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
return loop(origin, node.key, childNodes)
|
||||
})
|
||||
} catch (error) {
|
||||
window.message.error(t('chat.topics.export.obsidian_fetch_failed'))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理开关切换
|
||||
const handleSwitchChange = (checked: boolean) => {
|
||||
setShowMdFiles(checked)
|
||||
// 重置选择
|
||||
setCurrentSelection({
|
||||
path: defaultPath,
|
||||
isMdFile: false
|
||||
})
|
||||
onPathChange(defaultPath, false)
|
||||
|
||||
// 重置Tree状态并强制重新渲染
|
||||
setTreeData([])
|
||||
setExpandedKeys(['/'])
|
||||
|
||||
// 递增key值以强制Tree组件完全重新渲染
|
||||
setTreeKey((prev) => prev + 1)
|
||||
|
||||
// 延迟初始化根节点,让状态完全清除
|
||||
setTimeout(() => {
|
||||
initializeRootNode()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
// 自定义图标,为md文件和文件夹显示不同的图标
|
||||
const renderIcon = (props: any) => {
|
||||
const { data } = props
|
||||
if (data.isMdFile) {
|
||||
return <FileOutlined />
|
||||
}
|
||||
return <FolderOutlined />
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SwitchContainer>
|
||||
<span>{t('chat.topics.export.obsidian_show_md_files')}</span>
|
||||
<Switch checked={showMdFiles} onChange={handleSwitchChange} />
|
||||
</SwitchContainer>
|
||||
<Spin spinning={loading}>
|
||||
<TreeContainer>
|
||||
<Tree
|
||||
key={treeKey} // 使用key来强制重新渲染
|
||||
defaultSelectedKeys={[defaultPath]}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={(keys) => setExpandedKeys(keys as string[])}
|
||||
treeData={treeData}
|
||||
loadData={loadData}
|
||||
onSelect={(selectedKeys, info) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
const path = selectedKeys[0] as string
|
||||
const isMdFile = !!(info.node as any).isMdFile
|
||||
|
||||
setCurrentSelection({
|
||||
path,
|
||||
isMdFile
|
||||
})
|
||||
|
||||
onPathChange?.(path, isMdFile)
|
||||
}
|
||||
}}
|
||||
showLine
|
||||
showIcon
|
||||
icon={renderIcon}
|
||||
/>
|
||||
</TreeContainer>
|
||||
</Spin>
|
||||
<div>
|
||||
{currentSelection.path !== defaultPath && (
|
||||
<SelectedPath>
|
||||
{t('chat.topics.export.obsidian_selected_path')}: {currentSelection.path}
|
||||
</SelectedPath>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
`
|
||||
|
||||
const TreeContainer = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
height: 320px;
|
||||
`
|
||||
|
||||
const SwitchContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 10px;
|
||||
`
|
||||
|
||||
const SelectedPath = styled.div`
|
||||
font-size: 12px;
|
||||
color: var(--color-text-2);
|
||||
margin-top: 5px;
|
||||
padding: 0 10px;
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
export default ObsidianFolderSelector
|
||||
@@ -1,68 +1,52 @@
|
||||
import ObsidianFolderSelector from '@renderer/components/ObsidianFolderSelector'
|
||||
import ObsidianExportDialog from '@renderer/components/ObsidianExportDialog'
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { exportMarkdownToObsidian } from '@renderer/utils/export'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
interface ObsidianExportOptions {
|
||||
title: string
|
||||
markdown: string
|
||||
processingMethod: string | '3' // 默认新增(存在就覆盖)
|
||||
}
|
||||
|
||||
// 用于显示 Obsidian 导出对话框
|
||||
/**
|
||||
* 配置Obsidian 笔记属性弹窗
|
||||
* @param options.title 标题
|
||||
* @param options.markdown markdown内容
|
||||
* @param options.processingMethod 处理方式
|
||||
* @returns
|
||||
*/
|
||||
const showObsidianExportDialog = async (options: ObsidianExportOptions): Promise<boolean> => {
|
||||
const { title, markdown } = options
|
||||
const obsidianUrl = store.getState().settings.obsidianUrl
|
||||
const obsidianApiKey = store.getState().settings.obsidianApiKey
|
||||
const obsidianValut = store.getState().settings.obsidianValut
|
||||
const obsidianFolder = store.getState().settings.obsidianFolder
|
||||
|
||||
if (!obsidianUrl || !obsidianApiKey) {
|
||||
if (!obsidianValut || !obsidianFolder) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_not_configured'))
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建一个状态变量来存储选择的路径
|
||||
let selectedPath = '/'
|
||||
let selectedIsMdFile = false
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const div = document.createElement('div')
|
||||
document.body.appendChild(div)
|
||||
const root = createRoot(div)
|
||||
|
||||
// 显示文件夹选择对话框
|
||||
return new Promise<boolean>((resolve) => {
|
||||
window.modal.confirm({
|
||||
title: i18n.t('chat.topics.export.obsidian_select_folder'),
|
||||
content: (
|
||||
<ObsidianFolderSelector
|
||||
defaultPath={selectedPath}
|
||||
obsidianUrl={obsidianUrl}
|
||||
obsidianApiKey={obsidianApiKey}
|
||||
onPathChange={(path, isMdFile) => {
|
||||
selectedPath = path
|
||||
selectedIsMdFile = isMdFile
|
||||
}}
|
||||
/>
|
||||
),
|
||||
width: 600,
|
||||
icon: null,
|
||||
closable: true,
|
||||
maskClosable: true,
|
||||
centered: true,
|
||||
okButtonProps: { type: 'primary' },
|
||||
okText: i18n.t('chat.topics.export.obsidian_select_folder.btn'),
|
||||
onOk: () => {
|
||||
// 如果选择的是md文件,则使用选择的文件名而不是传入的标题
|
||||
const fileName = selectedIsMdFile ? selectedPath.split('/').pop()?.replace('.md', '') : title
|
||||
|
||||
exportMarkdownToObsidian(fileName as string, markdown, selectedPath, selectedIsMdFile)
|
||||
resolve(true)
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_fetch_failed'))
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
const handleClose = (success: boolean) => {
|
||||
root.unmount()
|
||||
document.body.removeChild(div)
|
||||
resolve(success)
|
||||
}
|
||||
const obsidianTags = store.getState().settings.obsidianTages
|
||||
root.render(
|
||||
<ObsidianExportDialog
|
||||
title={options.title}
|
||||
markdown={options.markdown}
|
||||
obsidianTags={obsidianTags}
|
||||
processingMethod={options.processingMethod}
|
||||
open={true}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const ObsidianExportPopup = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PushpinOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { getModelLogo, isEmbeddingModel } from '@renderer/config/models'
|
||||
import { getModelLogo, isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
@@ -76,7 +76,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
// 根据输入的文本筛选模型
|
||||
const getFilteredModels = useCallback(
|
||||
(provider) => {
|
||||
let models = provider.models.filter((m) => !isEmbeddingModel(m))
|
||||
let models = provider.models.filter((m) => !isEmbeddingModel(m) && !isRerankModel(m))
|
||||
|
||||
if (searchText.trim()) {
|
||||
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
||||
|
||||
@@ -172,7 +172,7 @@ export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|
|
||||
|
||||
// Reasoning models
|
||||
export const REASONING_REGEX =
|
||||
/^(o\d+(?:-[\w-]+)?|.*\b(?:reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*)$/i
|
||||
/^(o\d+(?:-[\w-]+)?|.*\b(?:reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*)$/i
|
||||
|
||||
// Embedding models
|
||||
export const EMBEDDING_REGEX = /(?:^text-|embed|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina-clip|jina-embeddings)/i
|
||||
@@ -1102,6 +1102,12 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
name: 'GLM 4V',
|
||||
group: 'GLM-4v'
|
||||
},
|
||||
{
|
||||
id: 'glm-4v-flash',
|
||||
provider: 'zhipu',
|
||||
name: 'GLM-4V-Flash',
|
||||
group: 'GLM-4v'
|
||||
},
|
||||
{
|
||||
id: 'glm-4v-plus',
|
||||
provider: 'zhipu',
|
||||
@@ -1872,6 +1878,8 @@ export const TEXT_TO_IMAGES_MODELS_SUPPORT_IMAGE_ENHANCEMENT = [
|
||||
'stabilityai/stable-diffusion-xl-base-1.0'
|
||||
]
|
||||
|
||||
export const GENERATE_IMAGE_MODELS = ['gemini-2.0-flash-exp-image-generation', 'gemini-2.0-flash-exp']
|
||||
|
||||
export function isTextToImageModel(model: Model): boolean {
|
||||
return TEXT_TO_IMAGE_REGEX.test(model.id)
|
||||
}
|
||||
@@ -1889,14 +1897,15 @@ export function isEmbeddingModel(model: Model): boolean {
|
||||
return EMBEDDING_REGEX.test(model.name)
|
||||
}
|
||||
|
||||
if (isRerankModel(model)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return EMBEDDING_REGEX.test(model.id) || model.type?.includes('embedding') || false
|
||||
}
|
||||
|
||||
export function isRerankModel(model: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
return RERANKING_REGEX.test(model.id) || false
|
||||
return model ? RERANKING_REGEX.test(model.id) || false : false
|
||||
}
|
||||
|
||||
export function isVisionModel(model: Model): boolean {
|
||||
@@ -2002,6 +2011,28 @@ export function isWebSearchModel(model: Model): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
export function isGenerateImageModel(model: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
}
|
||||
|
||||
const provider = getProviderByModel(model)
|
||||
|
||||
if (!provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isEmbedding = isEmbeddingModel(model)
|
||||
|
||||
if (isEmbedding) {
|
||||
return false
|
||||
}
|
||||
if (GENERATE_IMAGE_MODELS.includes(model.id)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function getOpenAIWebSearchParams(assistant: Assistant, model: Model): Record<string, any> {
|
||||
if (isWebSearchModel(model)) {
|
||||
if (assistant.enableWebSearch) {
|
||||
|
||||
@@ -93,6 +93,8 @@ export function getProviderLogo(providerId: string) {
|
||||
return PROVIDER_LOGO_MAP[providerId as keyof typeof PROVIDER_LOGO_MAP]
|
||||
}
|
||||
|
||||
export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina']
|
||||
|
||||
export const PROVIDER_CONFIG = {
|
||||
openai: {
|
||||
api: {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { LanguageVarious } from '@renderer/types'
|
||||
import { ConfigProvider, theme } from 'antd'
|
||||
import elGR from 'antd/locale/el_GR'
|
||||
import enUS from 'antd/locale/en_US'
|
||||
import esES from 'antd/locale/es_ES'
|
||||
import frFR from 'antd/locale/fr_FR'
|
||||
import jaJP from 'antd/locale/ja_JP'
|
||||
import ptPT from 'antd/locale/pt_PT'
|
||||
import ruRU from 'antd/locale/ru_RU'
|
||||
import zhCN from 'antd/locale/zh_CN'
|
||||
import zhTW from 'antd/locale/zh_TW'
|
||||
@@ -53,7 +57,14 @@ function getAntdLocale(language: LanguageVarious) {
|
||||
return ruRU
|
||||
case 'ja-JP':
|
||||
return jaJP
|
||||
|
||||
case 'el-GR':
|
||||
return elGR
|
||||
case 'es-ES':
|
||||
return esES
|
||||
case 'fr-FR':
|
||||
return frFR
|
||||
case 'pt-PT':
|
||||
return ptPT
|
||||
default:
|
||||
return zhCN
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
SendMessageShortcut,
|
||||
setLaunchOnBoot,
|
||||
setLaunchToTray,
|
||||
setSendMessageShortcut as _setSendMessageShortcut,
|
||||
setShowAssistantIcon,
|
||||
setSidebarIcons,
|
||||
@@ -8,7 +10,8 @@ import {
|
||||
setTheme,
|
||||
SettingsState,
|
||||
setTopicPosition,
|
||||
setTray,
|
||||
setTray as _setTray,
|
||||
setTrayOnClose,
|
||||
setWindowStyle
|
||||
} from '@renderer/store/settings'
|
||||
import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
||||
@@ -22,10 +25,30 @@ export function useSettings() {
|
||||
setSendMessageShortcut(shortcut: SendMessageShortcut) {
|
||||
dispatch(_setSendMessageShortcut(shortcut))
|
||||
},
|
||||
setTray(isActive: boolean) {
|
||||
dispatch(setTray(isActive))
|
||||
window.api.setTray(isActive)
|
||||
|
||||
setLaunch(isLaunchOnBoot: boolean | undefined, isLaunchToTray: boolean | undefined = undefined) {
|
||||
if (isLaunchOnBoot !== undefined) {
|
||||
dispatch(setLaunchOnBoot(isLaunchOnBoot))
|
||||
window.api.setLaunchOnBoot(isLaunchOnBoot)
|
||||
}
|
||||
|
||||
if (isLaunchToTray !== undefined) {
|
||||
dispatch(setLaunchToTray(isLaunchToTray))
|
||||
window.api.setLaunchToTray(isLaunchToTray)
|
||||
}
|
||||
},
|
||||
|
||||
setTray(isShowTray: boolean | undefined, isTrayOnClose: boolean | undefined = undefined) {
|
||||
if (isShowTray !== undefined) {
|
||||
dispatch(_setTray(isShowTray))
|
||||
window.api.setTray(isShowTray)
|
||||
}
|
||||
if (isTrayOnClose !== undefined) {
|
||||
dispatch(setTrayOnClose(isTrayOnClose))
|
||||
window.api.setTrayOnClose(isTrayOnClose)
|
||||
}
|
||||
},
|
||||
|
||||
setTheme(theme: ThemeMode) {
|
||||
dispatch(setTheme(theme))
|
||||
},
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
// Original translation
|
||||
import enUS from './locales/en-us.json'
|
||||
import jaJP from './locales/ja-jp.json'
|
||||
import ruRU from './locales/ru-ru.json'
|
||||
import zhCN from './locales/zh-cn.json'
|
||||
import zhTW from './locales/zh-tw.json'
|
||||
// Machine translation
|
||||
import elGR from './translate/el-gr.json'
|
||||
import esES from './translate/es-es.json'
|
||||
import frFR from './translate/fr-fr.json'
|
||||
import ptPT from './translate/pt-pt.json'
|
||||
|
||||
const resources = {
|
||||
'el-GR': elGR,
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
'es-ES': esES,
|
||||
'fr-FR': frFR,
|
||||
'ja-JP': jaJP,
|
||||
'ru-RU': ruRU
|
||||
'pt-PT': ptPT,
|
||||
'ru-RU': ruRU,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW
|
||||
}
|
||||
|
||||
export const getLanguage = () => {
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
"input.estimated_tokens.tip": "Estimated tokens",
|
||||
"input.expand": "Expand",
|
||||
"input.file_not_supported": "Model does not support this file type",
|
||||
"input.generate_image": "Generate image",
|
||||
"input.generate_image_not_supported": "The model does not support generating images.",
|
||||
"input.knowledge_base": "Knowledge Base",
|
||||
"input.new.context": "Clear Context {{Command}}",
|
||||
"input.new_topic": "New Topic {{Command}}",
|
||||
@@ -126,6 +128,12 @@
|
||||
"message.quote": "Quote",
|
||||
"message.regenerate.model": "Switch Model",
|
||||
"message.useful": "Helpful",
|
||||
"navigation": {
|
||||
"first": "Already at the first message",
|
||||
"last": "Already at the last message",
|
||||
"next": "Next Message",
|
||||
"prev": "Previous Message"
|
||||
},
|
||||
"resend": "Resend",
|
||||
"save": "Save",
|
||||
"settings.code_collapsible": "Code block collapsible",
|
||||
@@ -157,37 +165,42 @@
|
||||
"topics.edit.placeholder": "Enter new name",
|
||||
"topics.edit.title": "Edit Name",
|
||||
"topics.export.image": "Export as image",
|
||||
"topics.export.joplin": "Export to Joplin",
|
||||
"topics.export.md": "Export as markdown",
|
||||
"topics.export.notion": "Export to Notion",
|
||||
"topics.export.obsidian": "Export to Obsidian",
|
||||
"topics.export.obsidian_atributes": "Configure Note Attributes",
|
||||
"topics.export.obsidian_btn": "Confirm",
|
||||
"topics.export.obsidian_created": "Creation Time",
|
||||
"topics.export.obsidian_created_placeholder": "Please select the creation time",
|
||||
"topics.export.obsidian_export_failed": "Export failed",
|
||||
"topics.export.obsidian_export_success": "Export success",
|
||||
"topics.export.obsidian_not_configured": "Obsidian not configured",
|
||||
"topics.export.obsidian_operate": "Operation Method",
|
||||
"topics.export.obsidian_operate_append": "Append",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "Create New (Overwrite if it exists)",
|
||||
"topics.export.obsidian_operate_placeholder": "Please select the operation method",
|
||||
"topics.export.obsidian_operate_prepend": "Prepend",
|
||||
"topics.export.obsidian_source": "Source",
|
||||
"topics.export.obsidian_source_placeholder": "Please enter the source",
|
||||
"topics.export.obsidian_tags": "Tags",
|
||||
"topics.export.obsidian_tags_placeholder": "Please enter tags, separate multiple tags with commas",
|
||||
"topics.export.obsidian_title": "Title",
|
||||
"topics.export.obsidian_title_placeholder": "Please enter the title",
|
||||
"topics.export.obsidian_title_required": "The title cannot be empty",
|
||||
"topics.export.title": "Export",
|
||||
"topics.export.word": "Export as Word",
|
||||
"topics.export.yuque": "Export to Yuque",
|
||||
"topics.export.obsidian": "Export to Obsidian",
|
||||
"topics.export.obsidian_not_configured": "Obsidian not configured",
|
||||
"topics.export.obsidian_fetch_failed": "Failed to fetch Obsidian folder structure",
|
||||
"topics.export.obsidian_select_folder": "Select Obsidian folder",
|
||||
"topics.export.obsidian_select_folder.btn": "Confirm",
|
||||
"topics.export.obsidian_export_success": "Export success",
|
||||
"topics.export.obsidian_export_failed": "Export failed",
|
||||
"topics.export.obsidian_show_md_files": "Show MD Files",
|
||||
"topics.export.obsidian_selected_path": "Selected Path",
|
||||
"topics.export.joplin": "Export to Joplin",
|
||||
"topics.list": "Topic List",
|
||||
"topics.move_to": "Move to",
|
||||
"topics.new": "New Topic",
|
||||
"topics.pinned": "Pinned Topics",
|
||||
"topics.prompt": "Topic Prompts",
|
||||
"topics.prompt.edit.title": "Edit Topic Prompts",
|
||||
"topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic",
|
||||
"topics.title": "Topics",
|
||||
"topics.unpinned": "Unpinned Topics",
|
||||
"topics.new": "New Topic",
|
||||
"translate": "Translate",
|
||||
"navigation": {
|
||||
"prev": "Previous Message",
|
||||
"next": "Next Message",
|
||||
"first": "Already at the first message",
|
||||
"last": "Already at the last message"
|
||||
}
|
||||
"translate": "Translate"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Collapse",
|
||||
@@ -197,6 +210,7 @@
|
||||
},
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"advanced_settings": "Advanced Settings",
|
||||
"and": "and",
|
||||
"assistant": "Assistant",
|
||||
"avatar": "Avatar",
|
||||
@@ -205,6 +219,8 @@
|
||||
"chat": "Chat",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"cut": "Cut",
|
||||
"default": "Default",
|
||||
@@ -214,6 +230,7 @@
|
||||
"download": "Download",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Edit",
|
||||
"expand": "Expand",
|
||||
"footnote": "Reference content",
|
||||
"footnotes": "References",
|
||||
"fullscreen": "Entered fullscreen mode. Press F11 to exit",
|
||||
@@ -221,6 +238,7 @@
|
||||
"language": "Language",
|
||||
"model": "Model",
|
||||
"models": "Models",
|
||||
"more": "More",
|
||||
"name": "Name",
|
||||
"paste": "Paste",
|
||||
"prompt": "Prompt",
|
||||
@@ -233,12 +251,7 @@
|
||||
"select": "Select",
|
||||
"topics": "Topics",
|
||||
"warning": "Warning",
|
||||
"you": "You",
|
||||
"copied": "Copied",
|
||||
"confirm": "Confirm",
|
||||
"more": "More",
|
||||
"advanced_settings": "Advanced Settings",
|
||||
"expand": "Expand"
|
||||
"you": "You"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Docs"
|
||||
@@ -296,6 +309,12 @@
|
||||
"title": "Files",
|
||||
"type": "Type"
|
||||
},
|
||||
"gpustack": {
|
||||
"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": "GPUStack"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continue Chatting",
|
||||
"locate.message": "Locate the message",
|
||||
@@ -365,13 +384,13 @@
|
||||
"threshold_too_large_or_small": "Threshold cannot be greater than 1 or less than 0",
|
||||
"threshold_tooltip": "Used to evaluate the relevance between the user's question and the content in the knowledge base (0-1)",
|
||||
"title": "Knowledge Base",
|
||||
"topN": "Number of results returned",
|
||||
"topN__too_large_or_small": "The number of results returned cannot be greater than 100 or less than 1.",
|
||||
"topN_placeholder": "Not set",
|
||||
"topN_tooltip": "The number of matching results returned; the larger the value, the more matching results, but also the more tokens consumed.",
|
||||
"url_added": "URL added",
|
||||
"url_placeholder": "Enter URL, multiple URLs separated by Enter",
|
||||
"urls": "URLs",
|
||||
"topN": "Number of results returned",
|
||||
"topN_placeholder": "Not set",
|
||||
"topN__too_large_or_small": "The number of results returned cannot be greater than 100 or less than 1.",
|
||||
"topN_tooltip": "The number of matching results returned; the larger the value, the more matching results, but also the more tokens consumed."
|
||||
"urls": "URLs"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Arabic",
|
||||
@@ -409,22 +428,22 @@
|
||||
"title": "Mermaid Diagram"
|
||||
},
|
||||
"message": {
|
||||
"attachments": {
|
||||
"pasted_text": "Pasted Text",
|
||||
"pasted_image": "Pasted Image"
|
||||
},
|
||||
"api.check.model.title": "Select the model to use for detection",
|
||||
"api.connection.failed": "Connection failed",
|
||||
"api.connection.success": "Connection successful",
|
||||
"assistant.added.content": "Assistant added successfully",
|
||||
"attachments": {
|
||||
"pasted_image": "Pasted Image",
|
||||
"pasted_text": "Pasted Text"
|
||||
},
|
||||
"backup.failed": "Backup failed",
|
||||
"backup.start.success": "Backup started",
|
||||
"backup.success": "Backup successful",
|
||||
"chat.completion.paused": "Chat completion paused",
|
||||
"citations": "References",
|
||||
"copied": "Copied!",
|
||||
"copy.success": "Copied!",
|
||||
"copy.failed": "Copy failed",
|
||||
"copy.success": "Copied!",
|
||||
"error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size",
|
||||
"error.dimension_too_large": "Content size is too large",
|
||||
"error.enter.api.host": "Please enter your API host first",
|
||||
@@ -437,18 +456,20 @@
|
||||
"error.invalid.enter.model": "Please select a model",
|
||||
"error.invalid.proxy.url": "Invalid proxy URL",
|
||||
"error.invalid.webdav": "Invalid WebDAV settings",
|
||||
"error.joplin.export": "Failed to export to Joplin. Please keep Joplin running and check connection status or configuration",
|
||||
"error.joplin.no_config": "Joplin Authorization Token or URL is not configured",
|
||||
"error.markdown.export.preconf": "Failed to export the Markdown file to the preconfigured path",
|
||||
"error.markdown.export.specified": "Failed to export the Markdown file",
|
||||
"error.notion.export": "Failed to export to Notion. Please check connection status and configuration according to documentation",
|
||||
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
|
||||
"error.yuque.export": "Failed to export to Yuque. Please check connection status and configuration according to documentation",
|
||||
"error.yuque.no_config": "Yuque Token or Yuque Url is not configured",
|
||||
"error.joplin.no_config": "Joplin Authorization Token or URL is not configured",
|
||||
"error.joplin.export": "Failed to export to Joplin. Please keep Joplin running and check connection status or configuration",
|
||||
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
|
||||
"group.delete.title": "Delete Group Message",
|
||||
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",
|
||||
"info.notion.block_reach_limit": "Dialogue too long, exporting to Notion in pages",
|
||||
"loading.notion.exporting_progress": "Exporting to Notion ({{current}}/{{total}})...",
|
||||
"loading.notion.preparing": "Preparing to export to Notion...",
|
||||
"mention.title": "Switch model answer",
|
||||
"message.code_style": "Code style",
|
||||
"message.delete.content": "Are you sure you want to delete this message?",
|
||||
@@ -463,8 +484,6 @@
|
||||
"message.style": "Message style",
|
||||
"message.style.bubble": "Bubble",
|
||||
"message.style.plain": "Plain",
|
||||
"loading.notion.preparing": "Preparing to export to Notion...",
|
||||
"loading.notion.exporting_progress": "Exporting to Notion ({{current}}/{{total}})...",
|
||||
"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?",
|
||||
@@ -473,22 +492,22 @@
|
||||
"restore.success": "Restored successfully",
|
||||
"save.success.title": "Saved successfully",
|
||||
"searching": "Searching the internet...",
|
||||
"success.joplin.export": "Successfully exported to Joplin",
|
||||
"success.markdown.export.preconf": "Successfully exported the Markdown file to the preconfigured path",
|
||||
"success.markdown.export.specified": "Successfully exported the Markdown file",
|
||||
"success.notion.export": "Successfully exported to Notion",
|
||||
"success.yuque.export": "Successfully exported to Yuque",
|
||||
"success.joplin.export": "Successfully exported to Joplin",
|
||||
"switch.disabled": "Please wait for the current reply to complete",
|
||||
"tools": {
|
||||
"completed": "Completed",
|
||||
"invoking": "Invoking"
|
||||
},
|
||||
"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",
|
||||
"warn.notion.exporting": "Exporting to Notion, please do not request export repeatedly!",
|
||||
"warning.rate.limit": "Too many requests. Please wait {{seconds}} seconds before trying again.",
|
||||
"tools": {
|
||||
"invoking": "Invoking",
|
||||
"completed": "Completed"
|
||||
}
|
||||
"warning.rate.limit": "Too many requests. Please wait {{seconds}} seconds before trying again."
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Add to sidebar",
|
||||
@@ -527,7 +546,7 @@
|
||||
"embedding": "Embedding",
|
||||
"embedding_model": "Embedding Model",
|
||||
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage",
|
||||
"free": "Free",
|
||||
"function_calling": "Function Calling",
|
||||
"no_matches": "No models available",
|
||||
"parameter_name": "Parameter Name",
|
||||
"parameter_type": {
|
||||
@@ -537,22 +556,22 @@
|
||||
"string": "Text"
|
||||
},
|
||||
"pinned": "Pinned",
|
||||
"reasoning": "Reasoning",
|
||||
"rerank_model": "Reordering Model",
|
||||
"rerank_model_support_provider": "Currently, the reordering model only supports some providers ({{provider}})",
|
||||
"rerank_model_tooltip": "Click the Manage button in Settings -> Model Services to add.",
|
||||
"search": "Search models...",
|
||||
"stream_output": "Stream output",
|
||||
"function_calling": "Function Calling",
|
||||
"type": {
|
||||
"embedding": "Embedding",
|
||||
"free": "Free",
|
||||
"function_calling": "Tool",
|
||||
"reasoning": "Reasoning",
|
||||
"rerank": "Reordering",
|
||||
"select": "Select Model Types",
|
||||
"text": "Text",
|
||||
"vision": "Vision",
|
||||
"function_calling": "Function Calling"
|
||||
},
|
||||
"vision": "Vision",
|
||||
"websearch": "WebSearch",
|
||||
"rerank_model": "Reordering Model",
|
||||
"rerank_model_tooltip": "Click the Manage button in Settings -> Model Services to add."
|
||||
"websearch": "WebSearch"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Expand Dialog",
|
||||
@@ -598,12 +617,6 @@
|
||||
},
|
||||
"title": "PlantUML Diagram"
|
||||
},
|
||||
"gpustack": {
|
||||
"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": "GPUStack"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "Explain this concept to me",
|
||||
"summarize": "Summarize this text",
|
||||
@@ -611,10 +624,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"alayanew": "Alaya NeW",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "Baichuan",
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
"deepseek": "DeepSeek",
|
||||
"dmxapi": "DMXAPI",
|
||||
@@ -623,6 +638,7 @@
|
||||
"gemini": "Gemini",
|
||||
"gitee-ai": "Gitee AI",
|
||||
"github": "GitHub Models",
|
||||
"gpustack": "GPUStack",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
@@ -651,10 +667,7 @@
|
||||
"xirang": "State Cloud Xirang",
|
||||
"yi": "Yi",
|
||||
"zhinao": "360AI",
|
||||
"zhipu": "ZHIPU AI",
|
||||
"copilot": "GitHub Copilot",
|
||||
"gpustack": "GPUStack",
|
||||
"alayanew": "Alaya NeW"
|
||||
"zhipu": "ZHIPU AI"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "Are you sure you want to restore data?",
|
||||
@@ -716,15 +729,30 @@
|
||||
"data.title": "Data Directory",
|
||||
"hour_interval_one": "{{count}} hour",
|
||||
"hour_interval_other": "{{count}} hours",
|
||||
"minute_interval_one": "{{count}} minute",
|
||||
"minute_interval_other": "{{count}} minutes",
|
||||
"markdown_export.title": "Markdown Export",
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "Check",
|
||||
"empty_token": "Please enter Joplin Authorization Token",
|
||||
"empty_url": "Please enter Joplin Clipper Service URL",
|
||||
"fail": "Joplin connection verification failed",
|
||||
"success": "Joplin connection verification successful"
|
||||
},
|
||||
"help": "In Joplin options, enable the web clipper (no browser extension needed), confirm the port, and copy the auth token here.",
|
||||
"title": "Joplin Configuration",
|
||||
"token": "Joplin Authorization Token",
|
||||
"token_placeholder": "Joplin Authorization Token",
|
||||
"url": "Joplin Web Clipper Service URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "When enabled, $$ will be forcibly used to mark LaTeX formulas when exporting to Markdown. Note: This option also affects all export methods through Markdown, such as Notion, Yuque, etc.",
|
||||
"markdown_export.force_dollar_math.title": "Force $$ for LaTeX formulas",
|
||||
"markdown_export.help": "If provided, exports will be automatically saved to this path; otherwise, a save dialog will appear.",
|
||||
"markdown_export.path": "Default Export Path",
|
||||
"markdown_export.path_placeholder": "Export Path",
|
||||
"markdown_export.select": "Select",
|
||||
"markdown_export.help": "If provided, exports will be automatically saved to this path; otherwise, a save dialog will appear.",
|
||||
"markdown_export.force_dollar_math.title": "Force $$ for LaTeX formulas",
|
||||
"markdown_export.force_dollar_math.help": "When enabled, $$ will be forcibly used to mark LaTeX formulas when exporting to Markdown. Note: This option also affects all export methods through Markdown, such as Notion, Yuque, etc.",
|
||||
"markdown_export.title": "Markdown Export",
|
||||
"minute_interval_one": "{{count}} minute",
|
||||
"minute_interval_other": "{{count}} minutes",
|
||||
"notion.api_key": "Notion API Key",
|
||||
"notion.api_key_placeholder": "Enter Notion API Key",
|
||||
"notion.auto_split": "Auto split when exporting",
|
||||
@@ -746,11 +774,22 @@
|
||||
"notion.split_size_help": "Recommended: 90 for Free plan, 24990 for Plus plan, default is 90",
|
||||
"notion.split_size_placeholder": "Enter block limit per page (default 90)",
|
||||
"notion.title": "Notion Configuration",
|
||||
"obsidian": {
|
||||
"folder": "Folder",
|
||||
"folder_placeholder": "Please enter the folder name",
|
||||
"tags": "Global Tags",
|
||||
"tags_placeholder": "Please enter the tag name, separate multiple tags with commas",
|
||||
"title": "Obsidian Configuration",
|
||||
"vault": "Vault",
|
||||
"vault_placeholder": "Please enter the vault name"
|
||||
},
|
||||
"title": "Data Settings",
|
||||
"webdav": {
|
||||
"autoSync": "Auto Backup",
|
||||
"autoSync.off": "Off",
|
||||
"backup.button": "Backup to WebDAV",
|
||||
"backup.modal.filename.placeholder": "Please enter backup filename",
|
||||
"backup.modal.title": "Backup to WebDAV",
|
||||
"host": "WebDAV Host",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} hour",
|
||||
@@ -762,14 +801,12 @@
|
||||
"password": "WebDAV Password",
|
||||
"path": "WebDAV Path",
|
||||
"path.placeholder": "/backup",
|
||||
"backup.modal.title": "Backup to WebDAV",
|
||||
"backup.modal.filename.placeholder": "Please enter backup filename",
|
||||
"restore.modal.title": "Restore from WebDAV",
|
||||
"restore.modal.select.placeholder": "Please select a backup file to restore",
|
||||
"restore.confirm.title": "Confirm Restore",
|
||||
"restore.confirm.content": "Restoring from WebDAV will overwrite current data. Do you want to continue?",
|
||||
"restore.button": "Restore from WebDAV",
|
||||
"restore.confirm.content": "Restoring from WebDAV will overwrite current data. Do you want to continue?",
|
||||
"restore.confirm.title": "Confirm Restore",
|
||||
"restore.content": "Restore from WebDAV will overwrite the current data, continue?",
|
||||
"restore.modal.select.placeholder": "Please select a backup file to restore",
|
||||
"restore.modal.title": "Restore from WebDAV",
|
||||
"restore.title": "Restore from WebDAV",
|
||||
"syncError": "Backup Error",
|
||||
"syncStatus": "Backup Status",
|
||||
@@ -790,36 +827,6 @@
|
||||
"title": "Yuque Configuration",
|
||||
"token": "Yuque Token",
|
||||
"token_placeholder": "Please enter the Yuque Token"
|
||||
},
|
||||
"obsidian": {
|
||||
"check": {
|
||||
"button": "Check",
|
||||
"empty_url": "Please enter the Obsidian REST API URL first",
|
||||
"empty_api_key": "Please enter the Obsidian API Key first",
|
||||
"fail": "Obsidian connection verification failed",
|
||||
"success": "Obsidian connection verification successful"
|
||||
},
|
||||
"help": "Install the Obsidian plugin Local REST API first, then get the Obsidian API Key",
|
||||
"url": "Obsidian Knowledge Base URL",
|
||||
"url_placeholder": "http://127.0.0.1:27123/",
|
||||
"title": "Obsidian Configuration",
|
||||
"api_key": "Obsidian API Key",
|
||||
"api_key_placeholder": "Please enter the Obsidian API Key"
|
||||
},
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "Check",
|
||||
"empty_url": "Please enter Joplin Clipper Service URL",
|
||||
"empty_token": "Please enter Joplin Authorization Token",
|
||||
"fail": "Joplin connection verification failed",
|
||||
"success": "Joplin connection verification successful"
|
||||
},
|
||||
"title": "Joplin Configuration",
|
||||
"help": "In Joplin options, enable the web clipper (no browser extension needed), confirm the port, and copy the auth token here.",
|
||||
"url": "Joplin Web Clipper Service URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/",
|
||||
"token": "Joplin Authorization Token",
|
||||
"token_placeholder": "Joplin Authorization Token"
|
||||
}
|
||||
},
|
||||
"display.assistant.title": "Assistant Settings",
|
||||
@@ -865,12 +872,15 @@
|
||||
"input.target_language.english": "English",
|
||||
"input.target_language.japanese": "Japanese",
|
||||
"input.target_language.russian": "Russian",
|
||||
"launch.onboot": "Start Automatically on Boot",
|
||||
"launch.title": "Launch",
|
||||
"launch.totray": "Minimize to Tray on Launch",
|
||||
"mcp": {
|
||||
"actions": "Actions",
|
||||
"active": "Active",
|
||||
"addError": "Failed to add server",
|
||||
"addServer": "Add Server",
|
||||
"addSuccess": "Server added successfully",
|
||||
"addError": "Failed to add server",
|
||||
"args": "Arguments",
|
||||
"argsTooltip": "Each argument on a new line",
|
||||
"baseUrlTooltip": "Remote server base URL",
|
||||
@@ -879,57 +889,53 @@
|
||||
"config_description": "Configure Model Context Protocol servers",
|
||||
"confirmDelete": "Delete Server",
|
||||
"confirmDeleteMessage": "Are you sure you want to delete the server?",
|
||||
"deleteSuccess": "Server deleted successfully",
|
||||
"deleteError": "Failed to delete server",
|
||||
"deleteSuccess": "Server deleted successfully",
|
||||
"dependenciesInstall": "Install Dependencies",
|
||||
"dependenciesInstalling": "Installing dependencies...",
|
||||
"description": "Description",
|
||||
"duplicateName": "A server with this name already exists",
|
||||
"editJson": "Edit JSON",
|
||||
"editServer": "Edit Server",
|
||||
"env": "Environment Variables",
|
||||
"envTooltip": "Format: KEY=value, one per line",
|
||||
"findMore": "Find More MCP Servers",
|
||||
"install": "Install",
|
||||
"installError": "Failed to install dependencies",
|
||||
"installSuccess": "Dependencies installed successfully",
|
||||
"jsonFormatError": "JSON formatting error",
|
||||
"jsonModeHint": "Edit the JSON representation of the MCP server configuration. Please ensure the format is correct before saving.",
|
||||
"jsonSaveError": "Failed to save JSON configuration.",
|
||||
"jsonSaveSuccess": "JSON configuration has been saved.",
|
||||
"missingDependencies": "is Missing, please install it to continue.",
|
||||
"name": "Name",
|
||||
"nameRequired": "Please enter a server name",
|
||||
"noServers": "No servers configured",
|
||||
"npx_list": {
|
||||
"actions": "Actions",
|
||||
"desc": "Search and add npm packages as MCP servers",
|
||||
"description": "Description",
|
||||
"no_packages": "No packages found",
|
||||
"npm": "NPM",
|
||||
"package_name": "Package Name",
|
||||
"scope_placeholder": "Enter npm scope (e.g. @your-org)",
|
||||
"scope_required": "Please enter npm scope",
|
||||
"search": "Search",
|
||||
"search_error": "Search error",
|
||||
"title": "NPX Package List",
|
||||
"usage": "Usage",
|
||||
"version": "Version"
|
||||
},
|
||||
"serverPlural": "servers",
|
||||
"serverSingular": "server",
|
||||
"title": "MCP Servers",
|
||||
"type": "Type",
|
||||
"updateSuccess": "Server updated successfully",
|
||||
"updateError": "Failed to update server",
|
||||
"url": "URL",
|
||||
"toggleError": "Toggle failed",
|
||||
"dependenciesInstalling": "Installing dependencies...",
|
||||
"dependenciesInstall": "Install Dependencies",
|
||||
"installSuccess": "Dependencies installed successfully",
|
||||
"installError": "Failed to install dependencies",
|
||||
"missingDependencies": "is Missing, please install it to continue.",
|
||||
"install": "Install",
|
||||
"npx_list": {
|
||||
"title": "NPX Package List",
|
||||
"desc": "Search and add npm packages as MCP servers",
|
||||
"scope_placeholder": "Enter npm scope (e.g. @your-org)",
|
||||
"search": "Search",
|
||||
"package_name": "Package Name",
|
||||
"description": "Description",
|
||||
"usage": "Usage",
|
||||
"npm": "NPM",
|
||||
"version": "Version",
|
||||
"actions": "Actions",
|
||||
"scope_required": "Please enter npm scope",
|
||||
"no_packages": "No packages found",
|
||||
"search_error": "Search error"
|
||||
},
|
||||
"editJson": "Edit JSON",
|
||||
"jsonModeHint": "Edit the JSON representation of the MCP server configuration. Please ensure the format is correct before saving.",
|
||||
"jsonFormatError": "JSON formatting error",
|
||||
"jsonSaveSuccess": "JSON configuration has been saved.",
|
||||
"jsonSaveError": "Failed to save JSON configuration."
|
||||
"type": "Type",
|
||||
"updateError": "Failed to update server",
|
||||
"updateSuccess": "Server updated successfully",
|
||||
"url": "URL"
|
||||
},
|
||||
"messages.divider": "Show divider between messages",
|
||||
"messages.navigation": "Message Navigation",
|
||||
"messages.navigation.none": "None",
|
||||
"messages.navigation.buttons": "Navigation Buttons",
|
||||
"messages.navigation.anchor": "Message Anchor",
|
||||
"messages.grid_columns": "Message grid display columns",
|
||||
"messages.grid_popover_trigger": "Grid detail trigger",
|
||||
"messages.grid_popover_trigger.click": "Click to display",
|
||||
@@ -943,6 +949,10 @@
|
||||
"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.navigation": "Message Navigation",
|
||||
"messages.navigation.anchor": "Message Anchor",
|
||||
"messages.navigation.buttons": "Navigation Buttons",
|
||||
"messages.navigation.none": "None",
|
||||
"messages.title": "Message Settings",
|
||||
"messages.use_serif_font": "Use serif font",
|
||||
"model": "Default Model",
|
||||
@@ -1005,43 +1015,43 @@
|
||||
"check": "Check",
|
||||
"check_all_keys": "Check All Keys",
|
||||
"check_multiple_keys": "Check Multiple API Keys",
|
||||
"copilot": {
|
||||
"auth_failed": "Github Copilot authentication failed.",
|
||||
"auth_success": "GitHub Copilot authentication successful.",
|
||||
"auth_success_title": "Certification successful.",
|
||||
"code_failed": "Failed to obtain Device Code, please try again.",
|
||||
"code_generated_desc": "Please copy the device code into the browser link below.",
|
||||
"code_generated_title": "Obtain Device Code",
|
||||
"confirm_login": "Excessive use may lead to your Github account being banned, please use it cautiously!!!!",
|
||||
"confirm_title": "Risk Warning",
|
||||
"connect": "Connect to Github",
|
||||
"custom_headers": "Custom request header",
|
||||
"description": "Your GitHub account needs to subscribe to Copilot.",
|
||||
"expand": "Expand",
|
||||
"headers_description": "Custom request headers (JSON format)",
|
||||
"invalid_json": "JSON format error",
|
||||
"login": "Log in to Github",
|
||||
"logout": "Exit GitHub",
|
||||
"logout_failed": "Exit failed, please try again.",
|
||||
"logout_success": "Successfully logged out.",
|
||||
"model_setting": "Model settings",
|
||||
"open_verification_first": "Please click the link above to access the verification page.",
|
||||
"rate_limit": "Rate limiting",
|
||||
"tooltip": "You need to log in to Github before using Github Copilot"
|
||||
},
|
||||
"delete.content": "Are you sure you want to delete this provider?",
|
||||
"delete.title": "Delete Provider",
|
||||
"docs_check": "Check",
|
||||
"docs_more_details": "for more details",
|
||||
"get_api_key": "Get API Key",
|
||||
"is_not_support_array_content": "Enable compatible mode",
|
||||
"no_models": "Please add models first before checking the API connection",
|
||||
"not_checked": "Not Checked",
|
||||
"remove_duplicate_keys": "Remove Duplicate Keys",
|
||||
"remove_invalid_keys": "Remove Invalid Keys",
|
||||
"search": "Search Providers...",
|
||||
"search_placeholder": "Search model id or name",
|
||||
"title": "Model Provider",
|
||||
"is_not_support_array_content": "Enable compatible mode",
|
||||
"copilot": {
|
||||
"tooltip": "You need to log in to Github before using Github Copilot",
|
||||
"description": "Your GitHub account needs to subscribe to Copilot.",
|
||||
"login": "Log in to Github",
|
||||
"connect": "Connect to Github",
|
||||
"logout": "Exit GitHub",
|
||||
"auth_success_title": "Certification successful.",
|
||||
"code_generated_title": "Obtain Device Code",
|
||||
"code_generated_desc": "Please copy the device code into the browser link below.",
|
||||
"code_failed": "Failed to obtain Device Code, please try again.",
|
||||
"auth_success": "GitHub Copilot authentication successful.",
|
||||
"auth_failed": "Github Copilot authentication failed.",
|
||||
"logout_success": "Successfully logged out.",
|
||||
"logout_failed": "Exit failed, please try again.",
|
||||
"confirm_title": "Risk Warning",
|
||||
"confirm_login": "Excessive use may lead to your Github account being banned, please use it cautiously!!!!",
|
||||
"rate_limit": "Rate limiting",
|
||||
"custom_headers": "Custom request header",
|
||||
"headers_description": "Custom request headers (JSON format)",
|
||||
"expand": "Expand",
|
||||
"model_setting": "Model settings",
|
||||
"invalid_json": "JSON format error",
|
||||
"open_verification_first": "Please click the link above to access the verification page."
|
||||
}
|
||||
"title": "Model Provider"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1056,9 +1066,9 @@
|
||||
"quickAssistant": {
|
||||
"click_tray_to_show": "Click the tray icon to start",
|
||||
"enable_quick_assistant": "Enable Quick Assistant",
|
||||
"read_clipboard_at_startup": "Read clipboard at startup",
|
||||
"title": "Quick Assistant",
|
||||
"use_shortcut_to_show": "Right-click the tray icon or use shortcuts to start",
|
||||
"read_clipboard_at_startup": "Read clipboard at startup"
|
||||
"use_shortcut_to_show": "Right-click the tray icon or use shortcuts to start"
|
||||
},
|
||||
"shortcuts": {
|
||||
"action": "Action",
|
||||
@@ -1095,14 +1105,18 @@
|
||||
"topic.position.left": "Left",
|
||||
"topic.position.right": "Right",
|
||||
"topic.show.time": "Show topic time",
|
||||
"tray.title": "Enable System Tray Icon",
|
||||
"tray.onclose": "Minimize to Tray on Close",
|
||||
"tray.show": "Show Tray Icon",
|
||||
"tray.title": "Tray",
|
||||
"websearch": {
|
||||
"blacklist": "Blacklist",
|
||||
"blacklist_description": "Results from the following websites will not appear in search results",
|
||||
"blacklist_tooltip": "Please use the following format (separated by line breaks)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
"check": "Check",
|
||||
"check_success": "Verification successful",
|
||||
"check_failed": "Verification failed",
|
||||
"check_success": "Verification successful",
|
||||
"enhance_mode": "Search enhance mode",
|
||||
"enhance_mode_tooltip": "Use the default model to extract search keywords from the problem and search",
|
||||
"get_api_key": "Get API Key",
|
||||
"no_provider_selected": "Please select a search service provider before checking.",
|
||||
"search_max_result": "Number of search results",
|
||||
@@ -1110,8 +1124,6 @@
|
||||
"search_provider_placeholder": "Choose a search service provider.",
|
||||
"search_result_default": "Default",
|
||||
"search_with_time": "Search with dates included",
|
||||
"enhance_mode": "Search enhance mode",
|
||||
"enhance_mode_tooltip": "Use the default model to extract search keywords from the problem and search",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API Key",
|
||||
"api_key.placeholder": "Enter Tavily API Key",
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
"input.estimated_tokens.tip": "推定トークン数",
|
||||
"input.expand": "展開",
|
||||
"input.file_not_supported": "モデルはこのファイルタイプをサポートしません",
|
||||
"input.generate_image": "画像を生成する",
|
||||
"input.generate_image_not_supported": "モデルは画像の生成をサポートしていません。",
|
||||
"input.knowledge_base": "ナレッジベース",
|
||||
"input.new.context": "コンテキストをクリア {{Command}}",
|
||||
"input.new_topic": "新しいトピック {{Command}}",
|
||||
@@ -126,6 +128,12 @@
|
||||
"message.quote": "引用",
|
||||
"message.regenerate.model": "モデルを切り替え",
|
||||
"message.useful": "役立つ",
|
||||
"navigation": {
|
||||
"first": "最初のメッセージです",
|
||||
"last": "最後のメッセージです",
|
||||
"next": "次のメッセージ",
|
||||
"prev": "前のメッセージ"
|
||||
},
|
||||
"resend": "再送信",
|
||||
"save": "保存",
|
||||
"settings.code_collapsible": "コードブロック折り畳み",
|
||||
@@ -157,37 +165,42 @@
|
||||
"topics.edit.placeholder": "新しい名前を入力",
|
||||
"topics.edit.title": "名前を編集",
|
||||
"topics.export.image": "画像としてエクスポート",
|
||||
"topics.export.joplin": "Joplin にエクスポート",
|
||||
"topics.export.md": "Markdownとしてエクスポート",
|
||||
"topics.export.notion": "Notion にエクスポート",
|
||||
"topics.export.obsidian": "Obsidian にエクスポート",
|
||||
"topics.export.obsidian_atributes": "ノートの属性を設定",
|
||||
"topics.export.obsidian_btn": "確定",
|
||||
"topics.export.obsidian_created": "作成日時",
|
||||
"topics.export.obsidian_created_placeholder": "作成日時を選択してください",
|
||||
"topics.export.obsidian_export_failed": "エクスポート失敗",
|
||||
"topics.export.obsidian_export_success": "エクスポート成功",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未設定",
|
||||
"topics.export.obsidian_operate": "処理方法",
|
||||
"topics.export.obsidian_operate_append": "追加",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "新規作成(既に存在する場合は上書き)",
|
||||
"topics.export.obsidian_operate_placeholder": "処理方法を選択してください",
|
||||
"topics.export.obsidian_operate_prepend": "先頭に追加",
|
||||
"topics.export.obsidian_source": "ソース",
|
||||
"topics.export.obsidian_source_placeholder": "ソースを入力してください",
|
||||
"topics.export.obsidian_tags": "タグ",
|
||||
"topics.export.obsidian_tags_placeholder": "タグを入力してください。複数のタグは英語のコンマで区切ってください",
|
||||
"topics.export.obsidian_title": "タイトル",
|
||||
"topics.export.obsidian_title_placeholder": "タイトルを入力してください",
|
||||
"topics.export.obsidian_title_required": "タイトルは空白にできません",
|
||||
"topics.export.title": "エクスポート",
|
||||
"topics.export.word": "Wordとしてエクスポート",
|
||||
"topics.export.yuque": "語雀にエクスポート",
|
||||
"topics.export.obsidian": "Obsidian にエクスポート",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未設定",
|
||||
"topics.export.obsidian_fetch_failed": "Obsidian ファイルフォルダ構造取得失敗",
|
||||
"topics.export.obsidian_select_folder": "Obsidian ファイルフォルダ選択",
|
||||
"topics.export.obsidian_select_folder.btn": "確定",
|
||||
"topics.export.obsidian_export_success": "エクスポート成功",
|
||||
"topics.export.obsidian_export_failed": "エクスポート失敗",
|
||||
"topics.export.obsidian_show_md_files": "mdファイルを表示",
|
||||
"topics.export.obsidian_selected_path": "選択済みパス",
|
||||
"topics.export.joplin": "Joplin にエクスポート",
|
||||
"topics.list": "トピックリスト",
|
||||
"topics.move_to": "移動先",
|
||||
"topics.new": "新しいトピック",
|
||||
"topics.pinned": "トピックを固定",
|
||||
"topics.prompt": "トピック提示語",
|
||||
"topics.prompt.edit.title": "トピック提示語を編集する",
|
||||
"topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供",
|
||||
"topics.title": "トピック",
|
||||
"topics.unpinned": "固定解除",
|
||||
"topics.new": "新しいトピック",
|
||||
"translate": "翻訳",
|
||||
"navigation": {
|
||||
"prev": "前のメッセージ",
|
||||
"next": "次のメッセージ",
|
||||
"first": "最初のメッセージです",
|
||||
"last": "最後のメッセージです"
|
||||
}
|
||||
"translate": "翻訳"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "折りたたむ",
|
||||
@@ -197,6 +210,7 @@
|
||||
},
|
||||
"common": {
|
||||
"add": "追加",
|
||||
"advanced_settings": "詳細設定",
|
||||
"and": "と",
|
||||
"assistant": "アシスタント",
|
||||
"avatar": "アバター",
|
||||
@@ -205,6 +219,8 @@
|
||||
"chat": "チャット",
|
||||
"clear": "クリア",
|
||||
"close": "閉じる",
|
||||
"confirm": "確認",
|
||||
"copied": "コピーされました",
|
||||
"copy": "コピー",
|
||||
"cut": "切り取り",
|
||||
"default": "デフォルト",
|
||||
@@ -214,6 +230,7 @@
|
||||
"download": "ダウンロード",
|
||||
"duplicate": "複製",
|
||||
"edit": "編集",
|
||||
"expand": "展開",
|
||||
"footnote": "引用内容",
|
||||
"footnotes": "脚注",
|
||||
"fullscreen": "全画面モードに入りました。F11キーで終了します",
|
||||
@@ -221,6 +238,7 @@
|
||||
"language": "言語",
|
||||
"model": "モデル",
|
||||
"models": "モデル",
|
||||
"more": "もっと",
|
||||
"name": "名前",
|
||||
"paste": "貼り付け",
|
||||
"prompt": "プロンプト",
|
||||
@@ -233,12 +251,7 @@
|
||||
"select": "選択",
|
||||
"topics": "トピック",
|
||||
"warning": "警告",
|
||||
"you": "あなた",
|
||||
"copied": "コピーされました",
|
||||
"confirm": "確認",
|
||||
"more": "もっと",
|
||||
"advanced_settings": "詳細設定",
|
||||
"expand": "展開"
|
||||
"you": "あなた"
|
||||
},
|
||||
"docs": {
|
||||
"title": "ドキュメント"
|
||||
@@ -296,6 +309,12 @@
|
||||
"title": "ファイル",
|
||||
"type": "タイプ"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
|
||||
"keep_alive_time.placeholder": "分",
|
||||
"keep_alive_time.title": "保持時間",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "チャットを続ける",
|
||||
"locate.message": "メッセージを探す",
|
||||
@@ -365,13 +384,13 @@
|
||||
"threshold_too_large_or_small": "しきい値は0より大きく1より小さい必要があります",
|
||||
"threshold_tooltip": "ユーザーの質問と知識ベースの内容の関連性を評価するためのしきい値(0-1)",
|
||||
"title": "ナレッジベース",
|
||||
"topN": "返却される結果の数",
|
||||
"topN__too_large_or_small": "結果の数は100より大きくてはならず、1より小さくてはなりません。",
|
||||
"topN_placeholder": "未設定",
|
||||
"topN_tooltip": "返されるマッチ結果の数は、数値が大きいほどマッチ結果が多くなりますが、消費されるトークンも増えます。",
|
||||
"url_added": "URLが追加されました",
|
||||
"url_placeholder": "URLを入力, 複数のURLはEnterで区切る",
|
||||
"urls": "URL",
|
||||
"topN": "返却される結果の数",
|
||||
"topN_placeholder": "未設定",
|
||||
"topN__too_large_or_small": "結果の数は100より大きくてはならず、1より小さくてはなりません。",
|
||||
"topN_tooltip": "返されるマッチ結果の数は、数値が大きいほどマッチ結果が多くなりますが、消費されるトークンも増えます。"
|
||||
"urls": "URL"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "アラビア語",
|
||||
@@ -409,22 +428,22 @@
|
||||
"title": "Mermaid図"
|
||||
},
|
||||
"message": {
|
||||
"attachments": {
|
||||
"pasted_text": "クリップボードファイル",
|
||||
"pasted_image": "クリップボード画像"
|
||||
},
|
||||
"api.check.model.title": "検出に使用するモデルを選択してください",
|
||||
"api.connection.failed": "接続に失敗しました",
|
||||
"api.connection.success": "接続に成功しました",
|
||||
"assistant.added.content": "アシスタントが追加されました",
|
||||
"attachments": {
|
||||
"pasted_image": "クリップボード画像",
|
||||
"pasted_text": "クリップボードファイル"
|
||||
},
|
||||
"backup.failed": "バックアップに失敗しました",
|
||||
"backup.start.success": "バックアップを開始しました",
|
||||
"backup.success": "バックアップに成功しました",
|
||||
"chat.completion.paused": "チャットの完了が一時停止されました",
|
||||
"citations": "参考文献",
|
||||
"copied": "コピーしました!",
|
||||
"copy.success": "コピーしました!",
|
||||
"copy.failed": "コピーに失敗しました",
|
||||
"copy.success": "コピーしました!",
|
||||
"error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません",
|
||||
"error.dimension_too_large": "内容のサイズが大きすぎます",
|
||||
"error.enter.api.host": "APIホストを入力してください",
|
||||
@@ -437,20 +456,20 @@
|
||||
"error.invalid.enter.model": "モデルを選択してください",
|
||||
"error.invalid.proxy.url": "無効なプロキシURL",
|
||||
"error.invalid.webdav": "無効なWebDAV設定",
|
||||
"error.joplin.export": "Joplin へのエクスポートに失敗しました。Joplin が実行中であることを確認してください",
|
||||
"error.joplin.no_config": "Joplin 認証トークン または URL が設定されていません",
|
||||
"error.markdown.export.preconf": "Markdown ファイルを事前設定されたパスにエクスポートできませんでした",
|
||||
"error.markdown.export.specified": "Markdown ファイルのエクスポートに失敗しました",
|
||||
"error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください",
|
||||
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
|
||||
"error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください",
|
||||
"error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません",
|
||||
"error.joplin.no_config": "Joplin 認証トークン または URL が設定されていません",
|
||||
"error.joplin.export": "Joplin へのエクスポートに失敗しました。Joplin が実行中であることを確認してください",
|
||||
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
|
||||
"group.delete.title": "分組メッセージを削除",
|
||||
"loading.notion.preparing": "Notionへのエクスポートを準備中...",
|
||||
"loading.notion.exporting_progress": "Notionにエクスポート中 ({{current}}/{{total}})...",
|
||||
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
|
||||
"info.notion.block_reach_limit": "会話が長すぎます。Notionにページごとにエクスポートしています",
|
||||
"loading.notion.exporting_progress": "Notionにエクスポート中 ({{current}}/{{total}})...",
|
||||
"loading.notion.preparing": "Notionへのエクスポートを準備中...",
|
||||
"mention.title": "モデルを切り替える",
|
||||
"message.code_style": "コードスタイル",
|
||||
"message.delete.content": "このメッセージを削除してもよろしいですか?",
|
||||
@@ -473,22 +492,22 @@
|
||||
"restore.success": "復元に成功しました",
|
||||
"save.success.title": "保存に成功しました",
|
||||
"searching": "インターネットで検索中...",
|
||||
"success.joplin.export": "Joplin へのエクスポートに成功しました",
|
||||
"success.markdown.export.preconf": "Markdown ファイルを事前設定されたパスに正常にエクスポートしました",
|
||||
"success.markdown.export.specified": "Markdown ファイルを正常にエクスポートしました",
|
||||
"success.notion.export": "Notionへのエクスポートに成功しました",
|
||||
"success.yuque.export": "語雀へのエクスポートに成功しました",
|
||||
"success.joplin.export": "Joplin へのエクスポートに成功しました",
|
||||
"switch.disabled": "現在の応答が完了するまで切り替えを無効にします",
|
||||
"tools": {
|
||||
"completed": "完了",
|
||||
"invoking": "呼び出し中"
|
||||
},
|
||||
"topic.added": "新しいトピックが追加されました",
|
||||
"upgrade.success.button": "再起動",
|
||||
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
|
||||
"upgrade.success.title": "アップグレードに成功しました",
|
||||
"warn.notion.exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! ",
|
||||
"warning.rate.limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。",
|
||||
"tools": {
|
||||
"invoking": "呼び出し中",
|
||||
"completed": "完了"
|
||||
}
|
||||
"warning.rate.limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "サイドバーに追加",
|
||||
@@ -527,7 +546,7 @@
|
||||
"embedding": "埋め込み",
|
||||
"embedding_model": "埋め込み模型",
|
||||
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
|
||||
"free": "無料",
|
||||
"function_calling": "関数呼び出し",
|
||||
"no_matches": "利用可能なモデルがありません",
|
||||
"parameter_name": "パラメータ名",
|
||||
"parameter_type": {
|
||||
@@ -537,22 +556,22 @@
|
||||
"string": "テキスト"
|
||||
},
|
||||
"pinned": "固定済み",
|
||||
"reasoning": "推論",
|
||||
"rerank_model": "再順序付けモデル",
|
||||
"rerank_model_support_provider": "現在の再順序付けモデルは、{{provider}} のみサポートしています",
|
||||
"rerank_model_tooltip": "設定->モデルサービスに移動し、管理ボタンをクリックして追加します。",
|
||||
"search": "モデルを検索...",
|
||||
"stream_output": "ストリーム出力",
|
||||
"function_calling": "関数呼び出し",
|
||||
"type": {
|
||||
"embedding": "埋め込み",
|
||||
"free": "無料",
|
||||
"function_calling": "ツール",
|
||||
"reasoning": "推論",
|
||||
"rerank": "再順序付け",
|
||||
"select": "モデルタイプを選択",
|
||||
"text": "テキスト",
|
||||
"vision": "画像",
|
||||
"function_calling": "関数呼び出し"
|
||||
},
|
||||
"vision": "画像",
|
||||
"websearch": "ウェブ検索",
|
||||
"rerank_model": "再順序付けモデル",
|
||||
"rerank_model_tooltip": "設定->モデルサービスに移動し、管理ボタンをクリックして追加します。"
|
||||
"websearch": "ウェブ検索"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "ダイアログを展開",
|
||||
@@ -598,12 +617,6 @@
|
||||
},
|
||||
"title": "PlantUML 図表"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)",
|
||||
"keep_alive_time.placeholder": "分",
|
||||
"keep_alive_time.title": "保持時間",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "この概念を説明してください",
|
||||
"summarize": "このテキストを要約してください",
|
||||
@@ -611,10 +624,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"alayanew": "Alaya NeW",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "百川",
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
"deepseek": "DeepSeek",
|
||||
"dmxapi": "DMXAPI",
|
||||
@@ -623,6 +638,7 @@
|
||||
"gemini": "Gemini",
|
||||
"gitee-ai": "Gitee AI",
|
||||
"github": "GitHub Models",
|
||||
"gpustack": "GPUStack",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
@@ -651,10 +667,7 @@
|
||||
"xirang": "天翼クラウド 息壤",
|
||||
"yi": "零一万物",
|
||||
"zhinao": "360智脳",
|
||||
"zhipu": "智譜AI",
|
||||
"copilot": "GitHub Copilot",
|
||||
"gpustack": "GPUStack",
|
||||
"alayanew": "Alaya NeW"
|
||||
"zhipu": "智譜AI"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "データを復元しますか?",
|
||||
@@ -716,15 +729,30 @@
|
||||
"data.title": "データディレクトリ",
|
||||
"hour_interval_one": "{{count}} 時間",
|
||||
"hour_interval_other": "{{count}} 時間",
|
||||
"minute_interval_one": "{{count}} 分",
|
||||
"minute_interval_other": "{{count}} 分",
|
||||
"markdown_export.title": "Markdown エクスポート",
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "確認",
|
||||
"empty_token": "Joplin 認証トークン を先に入力してください",
|
||||
"empty_url": "Joplin 剪輯服務 URL を先に入力してください",
|
||||
"fail": "Joplin 接続確認に失敗しました",
|
||||
"success": "Joplin 接続確認に成功しました"
|
||||
},
|
||||
"help": "Joplin オプションで、剪輯サービスを有効にしてください。ポート番号を確認し、認証トークンをコピーしてください",
|
||||
"title": "Joplin 設定",
|
||||
"token": "Joplin 認証トークン",
|
||||
"token_placeholder": "Joplin 認証トークンを入力してください",
|
||||
"url": "Joplin 剪輯服務 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "有効にすると、Markdownにエクスポートする際にLaTeX数式を$$で強制的にマークします。注意:この設定はNotion、Yuqueなど、Markdownを通じたすべてのエクスポート方法にも影響します。",
|
||||
"markdown_export.force_dollar_math.title": "LaTeX数式に$$を強制使用",
|
||||
"markdown_export.help": "入力された場合、エクスポート時に自動的にこのパスに保存されます。未入力の場合、保存ダイアログが表示されます。",
|
||||
"markdown_export.path": "デフォルトのエクスポートパス",
|
||||
"markdown_export.path_placeholder": "エクスポートパス",
|
||||
"markdown_export.select": "選択",
|
||||
"markdown_export.help": "入力された場合、エクスポート時に自動的にこのパスに保存されます。未入力の場合、保存ダイアログが表示されます。",
|
||||
"markdown_export.force_dollar_math.title": "LaTeX数式に$$を強制使用",
|
||||
"markdown_export.force_dollar_math.help": "有効にすると、Markdownにエクスポートする際にLaTeX数式を$$で強制的にマークします。注意:この設定はNotion、Yuqueなど、Markdownを通じたすべてのエクスポート方法にも影響します。",
|
||||
"markdown_export.title": "Markdown エクスポート",
|
||||
"minute_interval_one": "{{count}} 分",
|
||||
"minute_interval_other": "{{count}} 分",
|
||||
"notion.api_key": "Notion APIキー",
|
||||
"notion.api_key_placeholder": "Notion APIキーを入力してください",
|
||||
"notion.auto_split": "ダイアログをエクスポートすると自動ページ分割",
|
||||
@@ -746,11 +774,22 @@
|
||||
"notion.split_size_help": "Notion無料版ユーザーは90、有料版ユーザーは24990、デフォルトは90",
|
||||
"notion.split_size_placeholder": "ページごとのブロック数制限を入力してください(デフォルト90)",
|
||||
"notion.title": "Notion 設定",
|
||||
"obsidian": {
|
||||
"folder": "フォルダー",
|
||||
"folder_placeholder": "フォルダーの名前を入力してください",
|
||||
"tags": "グローバルタグ",
|
||||
"tags_placeholder": "タグの名前を入力してください。複数のタグは英語のコンマで区切ってください",
|
||||
"title": "Obsidian の設定",
|
||||
"vault": "ヴォールト(保管庫)",
|
||||
"vault_placeholder": "保管庫の名前を入力してください"
|
||||
},
|
||||
"title": "データ設定",
|
||||
"webdav": {
|
||||
"autoSync": "自動バックアップ",
|
||||
"autoSync.off": "オフ",
|
||||
"backup.button": "WebDAVにバックアップ",
|
||||
"backup.modal.filename.placeholder": "バックアップファイル名を入力してください",
|
||||
"backup.modal.title": "WebDAV にバックアップ",
|
||||
"host": "WebDAVホスト",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 時間",
|
||||
@@ -763,18 +802,16 @@
|
||||
"path": "WebDAVパス",
|
||||
"path.placeholder": "/backup",
|
||||
"restore.button": "WebDAVから復元",
|
||||
"restore.confirm.content": "WebDAV から復元すると現在のデータが上書きされます。続行しますか?",
|
||||
"restore.confirm.title": "復元を確認",
|
||||
"restore.content": "WebDAVから復元すると現在のデータが上書きされます。続行しますか?",
|
||||
"restore.modal.select.placeholder": "復元するバックアップファイルを選択してください",
|
||||
"restore.modal.title": "WebDAV から復元",
|
||||
"restore.title": "WebDAVから復元",
|
||||
"syncError": "バックアップエラー",
|
||||
"syncStatus": "バックアップ状態",
|
||||
"title": "WebDAV",
|
||||
"user": "WebDAVユーザー",
|
||||
"backup.modal.title": "WebDAV にバックアップ",
|
||||
"backup.modal.filename.placeholder": "バックアップファイル名を入力してください",
|
||||
"restore.modal.title": "WebDAV から復元",
|
||||
"restore.modal.select.placeholder": "復元するバックアップファイルを選択してください",
|
||||
"restore.confirm.title": "復元を確認",
|
||||
"restore.confirm.content": "WebDAV から復元すると現在のデータが上書きされます。続行しますか?"
|
||||
"user": "WebDAVユーザー"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -790,36 +827,6 @@
|
||||
"title": "Yuque設定",
|
||||
"token": "Yuqueトークン",
|
||||
"token_placeholder": "Yuqueトークンを入力してください"
|
||||
},
|
||||
"obsidian": {
|
||||
"check": {
|
||||
"button": "確認",
|
||||
"empty_url": "Obsidian REST API URL を先に入力してください",
|
||||
"empty_api_key": "Obsidian API Key を先に入力してください",
|
||||
"fail": "Obsidian 接続確認に失敗しました",
|
||||
"success": "Obsidian 接続確認に成功しました"
|
||||
},
|
||||
"help": "Obsidian プラグイン Local REST API を先にインストールしてください。その後、Obsidian API Key を取得してください",
|
||||
"url": "Obsidian 知識ベース URL",
|
||||
"url_placeholder": "http://127.0.0.1:27123/",
|
||||
"title": "Obsidian 設定",
|
||||
"api_key": "Obsidian API Key",
|
||||
"api_key_placeholder": "Obsidian API Key を入力してください"
|
||||
},
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "確認",
|
||||
"empty_url": "Joplin 剪輯服務 URL を先に入力してください",
|
||||
"empty_token": "Joplin 認証トークン を先に入力してください",
|
||||
"fail": "Joplin 接続確認に失敗しました",
|
||||
"success": "Joplin 接続確認に成功しました"
|
||||
},
|
||||
"title": "Joplin 設定",
|
||||
"help": "Joplin オプションで、剪輯サービスを有効にしてください。ポート番号を確認し、認証トークンをコピーしてください",
|
||||
"url": "Joplin 剪輯服務 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/",
|
||||
"token": "Joplin 認証トークン",
|
||||
"token_placeholder": "Joplin 認証トークンを入力してください"
|
||||
}
|
||||
},
|
||||
"display.assistant.title": "アシスタント設定",
|
||||
@@ -865,12 +872,15 @@
|
||||
"input.target_language.english": "英語",
|
||||
"input.target_language.japanese": "日本語",
|
||||
"input.target_language.russian": "ロシア語",
|
||||
"launch.onboot": "起動時に自動で開始",
|
||||
"launch.title": "起動",
|
||||
"launch.totray": "起動時にトレイに最小化",
|
||||
"mcp": {
|
||||
"actions": "操作",
|
||||
"active": "有効",
|
||||
"addError": "サーバーの追加に失敗しました",
|
||||
"addServer": "サーバーを追加",
|
||||
"addSuccess": "サーバーが正常に追加されました",
|
||||
"addError": "サーバーの追加に失敗しました",
|
||||
"args": "引数",
|
||||
"argsTooltip": "1行に1つの引数を入力してください",
|
||||
"baseUrlTooltip": "リモートURLアドレス",
|
||||
@@ -881,55 +891,51 @@
|
||||
"confirmDeleteMessage": "本当にこのサーバーを削除しますか?",
|
||||
"deleteError": "サーバーの削除に失敗しました",
|
||||
"deleteSuccess": "サーバーが正常に削除されました",
|
||||
"dependenciesInstall": "依存関係をインストール",
|
||||
"dependenciesInstalling": "依存関係をインストール中...",
|
||||
"description": "説明",
|
||||
"duplicateName": "同じ名前のサーバーが既に存在します",
|
||||
"editJson": "JSONを編集",
|
||||
"editServer": "サーバーを編集",
|
||||
"env": "環境変数",
|
||||
"envTooltip": "形式: KEY=value, 1行に1つ",
|
||||
"findMore": "MCP サーバーを見つける",
|
||||
"install": "インストール",
|
||||
"installError": "依存関係のインストールに失敗しました",
|
||||
"installSuccess": "依存関係のインストールに成功しました",
|
||||
"jsonFormatError": "JSONフォーマットエラー",
|
||||
"jsonModeHint": "MCPサーバー設定のJSON表現を編集します。保存する前に、フォーマットが正しいことを確認してください。",
|
||||
"jsonSaveError": "JSON設定の保存に失敗しました",
|
||||
"jsonSaveSuccess": "JSON設定が保存されました。",
|
||||
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
||||
"name": "名前",
|
||||
"nameRequired": "サーバー名を入力してください",
|
||||
"noServers": "サーバーが設定されていません",
|
||||
"npx_list": {
|
||||
"actions": "アクション",
|
||||
"desc": "npm パッケージを検索して MCP サーバーとして追加",
|
||||
"description": "説明",
|
||||
"no_packages": "パッケージが見つかりません",
|
||||
"npm": "NPM",
|
||||
"package_name": "パッケージ名",
|
||||
"scope_placeholder": "npm スコープを入力 (例: @your-org)",
|
||||
"scope_required": "npm スコープを入力してください",
|
||||
"search": "検索",
|
||||
"search_error": "パッケージの検索に失敗しました",
|
||||
"title": "NPX パッケージリスト",
|
||||
"usage": "使用法",
|
||||
"version": "バージョン"
|
||||
},
|
||||
"serverPlural": "サーバー",
|
||||
"serverSingular": "サーバー",
|
||||
"title": "MCP サーバー",
|
||||
"type": "タイプ",
|
||||
"updateSuccess": "サーバーが正常に更新されました",
|
||||
"updateError": "サーバーの更新に失敗しました",
|
||||
"url": "URL",
|
||||
"toggleError": "切り替えに失敗しました",
|
||||
"dependenciesInstalling": "依存関係をインストール中...",
|
||||
"dependenciesInstall": "依存関係をインストール",
|
||||
"installSuccess": "依存関係のインストールに成功しました",
|
||||
"installError": "依存関係のインストールに失敗しました",
|
||||
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
||||
"install": "インストール",
|
||||
"npx_list": {
|
||||
"title": "NPX パッケージリスト",
|
||||
"desc": "npm パッケージを検索して MCP サーバーとして追加",
|
||||
"scope_placeholder": "npm スコープを入力 (例: @your-org)",
|
||||
"search": "検索",
|
||||
"package_name": "パッケージ名",
|
||||
"description": "説明",
|
||||
"usage": "使用法",
|
||||
"npm": "NPM",
|
||||
"version": "バージョン",
|
||||
"actions": "アクション",
|
||||
"scope_required": "npm スコープを入力してください",
|
||||
"no_packages": "パッケージが見つかりません",
|
||||
"search_error": "パッケージの検索に失敗しました"
|
||||
},
|
||||
"editJson": "JSONを編集",
|
||||
"jsonModeHint": "MCPサーバー設定のJSON表現を編集します。保存する前に、フォーマットが正しいことを確認してください。",
|
||||
"jsonFormatError": "JSONフォーマットエラー",
|
||||
"jsonSaveSuccess": "JSON設定が保存されました。",
|
||||
"jsonSaveError": "JSON設定の保存に失敗しました"
|
||||
"type": "タイプ",
|
||||
"updateError": "サーバーの更新に失敗しました",
|
||||
"updateSuccess": "サーバーが正常に更新されました",
|
||||
"url": "URL"
|
||||
},
|
||||
"messages.divider": "メッセージ間に区切り線を表示",
|
||||
"messages.navigation": "メッセージナビゲーション",
|
||||
"messages.navigation.none": "表示しない",
|
||||
"messages.navigation.buttons": "上下ボタン",
|
||||
"messages.navigation.anchor": "会話アンカー",
|
||||
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||
"messages.grid_popover_trigger": "グリッド詳細トリガー",
|
||||
"messages.grid_popover_trigger.click": "クリックで表示",
|
||||
@@ -943,6 +949,10 @@
|
||||
"messages.math_engine": "数式エンジン",
|
||||
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
||||
"messages.model.title": "モデル設定",
|
||||
"messages.navigation": "メッセージナビゲーション",
|
||||
"messages.navigation.anchor": "会話アンカー",
|
||||
"messages.navigation.buttons": "上下ボタン",
|
||||
"messages.navigation.none": "表示しない",
|
||||
"messages.title": "メッセージ設定",
|
||||
"messages.use_serif_font": "セリフフォントを使用",
|
||||
"model": "デフォルトモデル",
|
||||
@@ -1005,43 +1015,43 @@
|
||||
"check": "チェック",
|
||||
"check_all_keys": "すべてのキーをチェック",
|
||||
"check_multiple_keys": "複数のAPIキーをチェック",
|
||||
"copilot": {
|
||||
"auth_failed": "Github Copilotの認証に失敗しました。",
|
||||
"auth_success": "Github Copilotの認証が成功しました",
|
||||
"auth_success_title": "認証成功",
|
||||
"code_failed": "デバイスコードの取得に失敗しました。再試行してください。",
|
||||
"code_generated_desc": "デバイスコードを下記のブラウザリンクにコピーしてください。",
|
||||
"code_generated_title": "デバイスコードを取得する",
|
||||
"confirm_login": "過度使用すると、あなたのGithubアカウントが停止される可能性があるため、慎重に使用してください!!!!",
|
||||
"confirm_title": "リスク警告",
|
||||
"connect": "GitHubに接続する",
|
||||
"custom_headers": "カスタムリクエストヘッダー",
|
||||
"description": "あなたのGithubアカウントはCopilotを購読する必要があります。",
|
||||
"expand": "展開",
|
||||
"headers_description": "カスタムリクエストヘッダー(JSONフォーマット)",
|
||||
"invalid_json": "JSONフォーマットエラー",
|
||||
"login": "GitHubにログインする",
|
||||
"logout": "GitHubから退出する",
|
||||
"logout_failed": "ログアウトに失敗しました。もう一度お試しください。",
|
||||
"logout_success": "正常にログアウトしました。",
|
||||
"model_setting": "モデル設定",
|
||||
"open_verification_first": "上のリンクをクリックして、確認ページにアクセスしてください。",
|
||||
"rate_limit": "レート制限",
|
||||
"tooltip": "Github Copilot を使用するには、まず Github にログインする必要があります。"
|
||||
},
|
||||
"delete.content": "このプロバイダーを削除してもよろしいですか?",
|
||||
"delete.title": "プロバイダーを削除",
|
||||
"docs_check": "チェック",
|
||||
"docs_more_details": "詳細を確認",
|
||||
"get_api_key": "APIキーを取得",
|
||||
"is_not_support_array_content": "互換モードを有効にする",
|
||||
"no_models": "API接続をチェックする前に、モデルを追加してください",
|
||||
"not_checked": "未チェック",
|
||||
"remove_duplicate_keys": "重複キーを削除",
|
||||
"remove_invalid_keys": "無効なキーを削除",
|
||||
"search": "プロバイダーを検索...",
|
||||
"search_placeholder": "モデルIDまたは名前を検索",
|
||||
"title": "モデルプロバイダー",
|
||||
"is_not_support_array_content": "互換モードを有効にする",
|
||||
"copilot": {
|
||||
"tooltip": "Github Copilot を使用するには、まず Github にログインする必要があります。",
|
||||
"description": "あなたのGithubアカウントはCopilotを購読する必要があります。",
|
||||
"login": "GitHubにログインする",
|
||||
"connect": "GitHubに接続する",
|
||||
"logout": "GitHubから退出する",
|
||||
"auth_success_title": "認証成功",
|
||||
"code_generated_title": "デバイスコードを取得する",
|
||||
"code_generated_desc": "デバイスコードを下記のブラウザリンクにコピーしてください。",
|
||||
"code_failed": "デバイスコードの取得に失敗しました。再試行してください。",
|
||||
"auth_success": "Github Copilotの認証が成功しました",
|
||||
"auth_failed": "Github Copilotの認証に失敗しました。",
|
||||
"logout_success": "正常にログアウトしました。",
|
||||
"logout_failed": "ログアウトに失敗しました。もう一度お試しください。",
|
||||
"confirm_title": "リスク警告",
|
||||
"confirm_login": "過度使用すると、あなたのGithubアカウントが停止される可能性があるため、慎重に使用してください!!!!",
|
||||
"rate_limit": "レート制限",
|
||||
"custom_headers": "カスタムリクエストヘッダー",
|
||||
"headers_description": "カスタムリクエストヘッダー(JSONフォーマット)",
|
||||
"expand": "展開",
|
||||
"model_setting": "モデル設定",
|
||||
"invalid_json": "JSONフォーマットエラー",
|
||||
"open_verification_first": "上のリンクをクリックして、確認ページにアクセスしてください。"
|
||||
}
|
||||
"title": "モデルプロバイダー"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1056,9 +1066,9 @@
|
||||
"quickAssistant": {
|
||||
"click_tray_to_show": "トレイアイコンをクリックして起動",
|
||||
"enable_quick_assistant": "クイックアシスタントを有効にする",
|
||||
"read_clipboard_at_startup": "起動時にクリップボードを読み取る",
|
||||
"title": "クイックアシスタント",
|
||||
"use_shortcut_to_show": "トレイアイコンを右クリックするか、ショートカットキーで起動できます",
|
||||
"read_clipboard_at_startup": "起動時にクリップボードを読み取る"
|
||||
"use_shortcut_to_show": "トレイアイコンを右クリックするか、ショートカットキーで起動できます"
|
||||
},
|
||||
"shortcuts": {
|
||||
"action": "操作",
|
||||
@@ -1095,14 +1105,18 @@
|
||||
"topic.position.left": "左",
|
||||
"topic.position.right": "右",
|
||||
"topic.show.time": "トピックの時間を表示",
|
||||
"tray.title": "システムトレイアイコンを有効にする",
|
||||
"tray.onclose": "閉じるときにトレイに最小化",
|
||||
"tray.show": "トレイアイコンを表示",
|
||||
"tray.title": "トレイ",
|
||||
"websearch": {
|
||||
"blacklist": "ブラックリスト",
|
||||
"blacklist_description": "以下のウェブサイトの結果は検索結果に表示されません",
|
||||
"blacklist_tooltip": "以下の形式を使用してください(改行区切り)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
"check": "チェック",
|
||||
"check_success": "検証に成功しました",
|
||||
"check_failed": "検証に失敗しました",
|
||||
"check_success": "検証に成功しました",
|
||||
"enhance_mode": "検索強化モード",
|
||||
"enhance_mode_tooltip": "デフォルトモデルを使用して問題から検索キーワードを抽出し、検索を実行します",
|
||||
"get_api_key": "APIキーを取得",
|
||||
"no_provider_selected": "検索サービスプロバイダーを選択してから再確認してください。",
|
||||
"search_max_result": "検索結果の数",
|
||||
@@ -1110,8 +1124,6 @@
|
||||
"search_provider_placeholder": "検索サービスプロバイダーを選択する",
|
||||
"search_result_default": "デフォルト",
|
||||
"search_with_time": "日付を含む検索",
|
||||
"enhance_mode": "検索強化モード",
|
||||
"enhance_mode_tooltip": "デフォルトモデルを使用して問題から検索キーワードを抽出し、検索を実行します",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API キー",
|
||||
"api_key.placeholder": "Tavily API キーを入力してください",
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
"input.estimated_tokens.tip": "Затраты токенов",
|
||||
"input.expand": "Развернуть",
|
||||
"input.file_not_supported": "Модель не поддерживает этот тип файла",
|
||||
"input.generate_image": "Сгенерировать изображение",
|
||||
"input.generate_image_not_supported": "Модель не поддерживает генерацию изображений.",
|
||||
"input.knowledge_base": "База знаний",
|
||||
"input.new.context": "Очистить контекст {{Command}}",
|
||||
"input.new_topic": "Новый топик {{Command}}",
|
||||
@@ -126,6 +128,12 @@
|
||||
"message.quote": "Цитата",
|
||||
"message.regenerate.model": "Переключить модель",
|
||||
"message.useful": "Полезно",
|
||||
"navigation": {
|
||||
"first": "Уже первое сообщение",
|
||||
"last": "Уже последнее сообщение",
|
||||
"next": "Следующее сообщение",
|
||||
"prev": "Предыдущее сообщение"
|
||||
},
|
||||
"resend": "Переотправить",
|
||||
"save": "Сохранить",
|
||||
"settings.code_collapsible": "Блок кода свернут",
|
||||
@@ -157,37 +165,42 @@
|
||||
"topics.edit.placeholder": "Введите новый заголовок",
|
||||
"topics.edit.title": "Редактировать заголовок",
|
||||
"topics.export.image": "Экспорт как изображение",
|
||||
"topics.export.joplin": "Экспорт в Joplin",
|
||||
"topics.export.md": "Экспорт как markdown",
|
||||
"topics.export.notion": "Экспорт в Notion",
|
||||
"topics.export.obsidian": "Экспорт в Obsidian",
|
||||
"topics.export.obsidian_atributes": "Настроить атрибуты заметки",
|
||||
"topics.export.obsidian_btn": "Подтвердить",
|
||||
"topics.export.obsidian_created": "Дата создания",
|
||||
"topics.export.obsidian_created_placeholder": "Пожалуйста, выберите дату создания",
|
||||
"topics.export.obsidian_export_failed": "Экспорт не удалось",
|
||||
"topics.export.obsidian_export_success": "Экспорт успешно завершен",
|
||||
"topics.export.obsidian_not_configured": "Obsidian не настроен",
|
||||
"topics.export.obsidian_operate": "Метод обработки",
|
||||
"topics.export.obsidian_operate_append": "Добавить в конец",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "Создать новый (перезаписать, если уже существует)",
|
||||
"topics.export.obsidian_operate_placeholder": "Пожалуйста, выберите метод обработки",
|
||||
"topics.export.obsidian_operate_prepend": "Добавить в начало",
|
||||
"topics.export.obsidian_source": "Источник",
|
||||
"topics.export.obsidian_source_placeholder": "Пожалуйста, введите источник",
|
||||
"topics.export.obsidian_tags": "Тэги",
|
||||
"topics.export.obsidian_tags_placeholder": "Пожалуйста, введите имена тегов. Разделяйте несколько тегов запятыми на английском языке",
|
||||
"topics.export.obsidian_title": "Заголовок",
|
||||
"topics.export.obsidian_title_placeholder": "Пожалуйста, введите заголовок",
|
||||
"topics.export.obsidian_title_required": "Заголовок не может быть пустым",
|
||||
"topics.export.title": "Экспорт",
|
||||
"topics.export.word": "Экспорт как Word",
|
||||
"topics.export.yuque": "Экспорт в Yuque",
|
||||
"topics.export.obsidian": "Экспорт в Obsidian",
|
||||
"topics.export.obsidian_not_configured": "Obsidian не настроен",
|
||||
"topics.export.obsidian_fetch_failed": "Не удалось получить структуру файлов Obsidian",
|
||||
"topics.export.obsidian_select_folder": "Выберите папку Obsidian",
|
||||
"topics.export.obsidian_select_folder.btn": "Определить",
|
||||
"topics.export.obsidian_export_success": "Экспорт успешно завершен",
|
||||
"topics.export.obsidian_export_failed": "Экспорт не удалось",
|
||||
"topics.export.obsidian_show_md_files": "Показать файлы MD",
|
||||
"topics.export.obsidian_selected_path": "Выбранный путь",
|
||||
"topics.export.joplin": "Экспорт в Joplin",
|
||||
"topics.list": "Список топиков",
|
||||
"topics.move_to": "Переместить в",
|
||||
"topics.new": "Новый топик",
|
||||
"topics.pinned": "Закрепленные темы",
|
||||
"topics.prompt": "Тематические подсказки",
|
||||
"topics.prompt.edit.title": "Редактировать подсказки темы",
|
||||
"topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы",
|
||||
"topics.title": "Топики",
|
||||
"topics.unpinned": "Открепленные темы",
|
||||
"topics.new": "Новый топик",
|
||||
"translate": "Перевести",
|
||||
"navigation": {
|
||||
"prev": "Предыдущее сообщение",
|
||||
"next": "Следующее сообщение",
|
||||
"first": "Уже первое сообщение",
|
||||
"last": "Уже последнее сообщение"
|
||||
}
|
||||
"translate": "Перевести"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Свернуть",
|
||||
@@ -197,6 +210,7 @@
|
||||
},
|
||||
"common": {
|
||||
"add": "Добавить",
|
||||
"advanced_settings": "Дополнительные настройки",
|
||||
"and": "и",
|
||||
"assistant": "Ассистент",
|
||||
"avatar": "Аватар",
|
||||
@@ -205,6 +219,8 @@
|
||||
"chat": "Чат",
|
||||
"clear": "Очистить",
|
||||
"close": "Закрыть",
|
||||
"confirm": "Подтверждение",
|
||||
"copied": "Скопировано",
|
||||
"copy": "Копировать",
|
||||
"cut": "Вырезать",
|
||||
"default": "По умолчанию",
|
||||
@@ -214,6 +230,7 @@
|
||||
"download": "Скачать",
|
||||
"duplicate": "Дублировать",
|
||||
"edit": "Редактировать",
|
||||
"expand": "Развернуть",
|
||||
"footnote": "Цитируемый контент",
|
||||
"footnotes": "Сноски",
|
||||
"fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода",
|
||||
@@ -221,6 +238,7 @@
|
||||
"language": "Язык",
|
||||
"model": "Модель",
|
||||
"models": "Модели",
|
||||
"more": "Ещё",
|
||||
"name": "Имя",
|
||||
"paste": "Вставить",
|
||||
"prompt": "Промпт",
|
||||
@@ -233,12 +251,7 @@
|
||||
"select": "Выбрать",
|
||||
"topics": "Топики",
|
||||
"warning": "Предупреждение",
|
||||
"you": "Вы",
|
||||
"confirm": "Подтверждение",
|
||||
"copied": "Скопировано",
|
||||
"more": "Ещё",
|
||||
"advanced_settings": "Дополнительные настройки",
|
||||
"expand": "Развернуть"
|
||||
"you": "Вы"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Документация"
|
||||
@@ -296,6 +309,12 @@
|
||||
"title": "Файлы",
|
||||
"type": "Тип"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
|
||||
"keep_alive_time.placeholder": "Минуты",
|
||||
"keep_alive_time.title": "Время жизни модели",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Продолжить чат",
|
||||
"locate.message": "Найти сообщение",
|
||||
@@ -365,13 +384,13 @@
|
||||
"threshold_too_large_or_small": "Порог не может быть больше 1 или меньше 0",
|
||||
"threshold_tooltip": "Используется для оценки соответствия между пользовательским вопросом и содержимым в базе знаний (0-1)",
|
||||
"title": "База знаний",
|
||||
"topN": "Количество возвращаемых результатов",
|
||||
"topN__too_large_or_small": "Количество возвращаемых результатов не может быть больше 100 или меньше 1.",
|
||||
"topN_placeholder": "Не установлено",
|
||||
"topN_tooltip": "Количество возвращаемых совпадений; чем больше значение, тем больше совпадений, но и потребление токенов тоже возрастает.",
|
||||
"url_added": "URL добавлен",
|
||||
"url_placeholder": "Введите URL, несколько URL через Enter",
|
||||
"urls": "URL-адреса",
|
||||
"topN": "Количество возвращаемых результатов",
|
||||
"topN_placeholder": "Не установлено",
|
||||
"topN__too_large_or_small": "Количество возвращаемых результатов не может быть больше 100 или меньше 1.",
|
||||
"topN_tooltip": "Количество возвращаемых совпадений; чем больше значение, тем больше совпадений, но и потребление токенов тоже возрастает."
|
||||
"urls": "URL-адреса"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Арабский",
|
||||
@@ -408,29 +427,23 @@
|
||||
},
|
||||
"title": "Диаграмма Mermaid"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
|
||||
"keep_alive_time.placeholder": "Минуты",
|
||||
"keep_alive_time.title": "Время жизни модели",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"message": {
|
||||
"attachments": {
|
||||
"pasted_text": "Вырезанный текст",
|
||||
"pasted_image": "Вырезанное изображение"
|
||||
},
|
||||
"api.check.model.title": "Выберите модель для проверки",
|
||||
"api.connection.failed": "Соединение не удалось",
|
||||
"api.connection.success": "Соединение успешно",
|
||||
"assistant.added.content": "Ассистент успешно добавлен",
|
||||
"attachments": {
|
||||
"pasted_image": "Вырезанное изображение",
|
||||
"pasted_text": "Вырезанный текст"
|
||||
},
|
||||
"backup.failed": "Создание резервной копии не удалось",
|
||||
"backup.start.success": "Создание резервной копии начато",
|
||||
"backup.success": "Резервная копия успешно создана",
|
||||
"chat.completion.paused": "Завершение чата приостановлено",
|
||||
"citations": "Источники",
|
||||
"copied": "Скопировано!",
|
||||
"copy.success": "Скопировано!",
|
||||
"copy.failed": "Не удалось скопировать",
|
||||
"copy.success": "Скопировано!",
|
||||
"error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.",
|
||||
"error.dimension_too_large": "Размер содержимого слишком велик",
|
||||
"error.enter.api.host": "Пожалуйста, введите ваш API хост",
|
||||
@@ -443,20 +456,20 @@
|
||||
"error.invalid.enter.model": "Пожалуйста, выберите модель",
|
||||
"error.invalid.proxy.url": "Неверный URL прокси",
|
||||
"error.invalid.webdav": "Неверные настройки WebDAV",
|
||||
"error.joplin.export": "Не удалось экспортировать в Joplin, пожалуйста, убедитесь, что Joplin запущен и проверьте состояние подключения или настройки",
|
||||
"error.joplin.no_config": "Joplin Authorization Token или URL не настроен",
|
||||
"error.markdown.export.preconf": "Не удалось экспортировать файл Markdown в предуказанный путь",
|
||||
"error.markdown.export.specified": "Не удалось экспортировать файл Markdown",
|
||||
"error.notion.export": "Ошибка экспорта в Notion, пожалуйста, проверьте состояние подключения и настройки в документации",
|
||||
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
|
||||
"error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации",
|
||||
"error.yuque.no_config": "Yuque Token или Yuque Url не настроен",
|
||||
"error.joplin.no_config": "Joplin Authorization Token или URL не настроен",
|
||||
"error.joplin.export": "Не удалось экспортировать в Joplin, пожалуйста, убедитесь, что Joplin запущен и проверьте состояние подключения или настройки",
|
||||
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
|
||||
"group.delete.title": "Удалить группу сообщений",
|
||||
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",
|
||||
"info.notion.block_reach_limit": "Диалог слишком длинный, экспортируется в Notion по страницам",
|
||||
"loading.notion.preparing": "Подготовка к экспорту в Notion...",
|
||||
"loading.notion.exporting_progress": "Экспорт в Notion ({{current}}/{{total}})...",
|
||||
"loading.notion.preparing": "Подготовка к экспорту в Notion...",
|
||||
"mention.title": "Переключить модель ответа",
|
||||
"message.code_style": "Стиль кода",
|
||||
"message.delete.content": "Вы уверены, что хотите удалить это сообщение?",
|
||||
@@ -479,22 +492,22 @@
|
||||
"restore.success": "Успешно восстановлено",
|
||||
"save.success.title": "Успешно сохранено",
|
||||
"searching": "Поиск в Интернете...",
|
||||
"success.joplin.export": "Успешный экспорт в Joplin",
|
||||
"success.markdown.export.preconf": "Файл Markdown успешно экспортирован в предуказанный путь",
|
||||
"success.markdown.export.specified": "Файл Markdown успешно экспортирован",
|
||||
"success.notion.export": "Успешный экспорт в Notion",
|
||||
"success.yuque.export": "Успешный экспорт в Yuque",
|
||||
"success.joplin.export": "Успешный экспорт в Joplin",
|
||||
"switch.disabled": "Пожалуйста, дождитесь завершения текущего ответа",
|
||||
"tools": {
|
||||
"completed": "Завершено",
|
||||
"invoking": "Вызов"
|
||||
},
|
||||
"topic.added": "Новый топик добавлен",
|
||||
"upgrade.success.button": "Перезапустить",
|
||||
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
|
||||
"upgrade.success.title": "Обновление успешно",
|
||||
"warn.notion.exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!",
|
||||
"warning.rate.limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова.",
|
||||
"tools": {
|
||||
"invoking": "Вызов",
|
||||
"completed": "Завершено"
|
||||
}
|
||||
"warning.rate.limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова."
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Добавить в боковую панель",
|
||||
@@ -533,7 +546,7 @@
|
||||
"embedding": "Встраиваемые",
|
||||
"embedding_model": "Встраиваемые модели",
|
||||
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",
|
||||
"free": "Бесплатные",
|
||||
"function_calling": "Вызов функции",
|
||||
"no_matches": "Нет доступных моделей",
|
||||
"parameter_name": "Имя параметра",
|
||||
"parameter_type": {
|
||||
@@ -543,22 +556,22 @@
|
||||
"string": "Текст"
|
||||
},
|
||||
"pinned": "Закреплено",
|
||||
"reasoning": "Рассуждение",
|
||||
"rerank_model": "Модель переупорядочивания",
|
||||
"rerank_model_support_provider": "Текущая модель переупорядочивания поддерживается только некоторыми поставщиками ({{provider}})",
|
||||
"rerank_model_tooltip": "В настройках -> Служба модели нажмите кнопку \"Управление\", чтобы добавить.",
|
||||
"search": "Поиск моделей...",
|
||||
"stream_output": "Потоковый вывод",
|
||||
"function_calling": "Вызов функции",
|
||||
"type": {
|
||||
"embedding": "Встраиваемые",
|
||||
"free": "Бесплатные",
|
||||
"function_calling": "Инструкция",
|
||||
"reasoning": "Рассуждение",
|
||||
"rerank": "Переупорядочить",
|
||||
"select": "Выберите тип модели",
|
||||
"text": "Текст",
|
||||
"vision": "Изображение",
|
||||
"function_calling": "Вызов функции"
|
||||
},
|
||||
"vision": "Визуальные",
|
||||
"websearch": "Веб-поисковые",
|
||||
"rerank_model": "Модель переупорядочивания",
|
||||
"rerank_model_tooltip": "В настройках -> Служба модели нажмите кнопку \"Управление\", чтобы добавить."
|
||||
"vision": "Визуальные",
|
||||
"websearch": "Веб-поисковые"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Развернуть диалоговое окно",
|
||||
@@ -611,10 +624,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"alayanew": "Alaya NeW",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "Baichuan",
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
"deepseek": "DeepSeek",
|
||||
"dmxapi": "DMXAPI",
|
||||
@@ -623,6 +638,7 @@
|
||||
"gemini": "Gemini",
|
||||
"gitee-ai": "Gitee AI",
|
||||
"github": "GitHub Models",
|
||||
"gpustack": "GPUStack",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
@@ -651,10 +667,7 @@
|
||||
"xirang": "State Cloud Xirang",
|
||||
"yi": "Yi",
|
||||
"zhinao": "360AI",
|
||||
"zhipu": "ZHIPU AI",
|
||||
"copilot": "GitHub Copilot",
|
||||
"gpustack": "GPUStack",
|
||||
"alayanew": "Alaya NeW"
|
||||
"zhipu": "ZHIPU AI"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "Вы уверены, что хотите восстановить данные?",
|
||||
@@ -716,15 +729,30 @@
|
||||
"data.title": "Каталог данных",
|
||||
"hour_interval_one": "{{count}} час",
|
||||
"hour_interval_other": "{{count}} часов",
|
||||
"minute_interval_one": "{{count}} минута",
|
||||
"minute_interval_other": "{{count}} минут",
|
||||
"markdown_export.title": "Экспорт в Markdown",
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "Проверить",
|
||||
"empty_token": "Сначала введите токен Joplin",
|
||||
"empty_url": "Сначала введите URL Joplin",
|
||||
"fail": "Не удалось проверить подключение к Joplin",
|
||||
"success": "Подключение к Joplin успешно проверено"
|
||||
},
|
||||
"help": "Включите Joplin опцию, проверьте порт и скопируйте токен",
|
||||
"title": "Настройка Joplin",
|
||||
"token": "Токен Joplin",
|
||||
"token_placeholder": "Введите токен Joplin",
|
||||
"url": "URL Joplin",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "Если включено, при экспорте в Markdown для обозначения формул LaTeX будет принудительно использоваться $$. Примечание: Эта опция также влияет на все методы экспорта через Markdown, такие как Notion, Yuque и т.д.",
|
||||
"markdown_export.force_dollar_math.title": "Принудительно использовать $$ для формул LaTeX",
|
||||
"markdown_export.help": "Если указано, файлы будут автоматически сохраняться в этот путь; в противном случае появится диалоговое окно сохранения.",
|
||||
"markdown_export.path": "Путь экспорта по умолчанию",
|
||||
"markdown_export.path_placeholder": "Путь экспорта",
|
||||
"markdown_export.select": "Выбрать",
|
||||
"markdown_export.help": "Если указано, файлы будут автоматически сохраняться в этот путь; в противном случае появится диалоговое окно сохранения.",
|
||||
"markdown_export.force_dollar_math.title": "Принудительно использовать $$ для формул LaTeX",
|
||||
"markdown_export.force_dollar_math.help": "Если включено, при экспорте в Markdown для обозначения формул LaTeX будет принудительно использоваться $$. Примечание: Эта опция также влияет на все методы экспорта через Markdown, такие как Notion, Yuque и т.д.",
|
||||
"markdown_export.title": "Экспорт в Markdown",
|
||||
"minute_interval_one": "{{count}} минута",
|
||||
"minute_interval_other": "{{count}} минут",
|
||||
"notion.api_key": "Ключ API Notion",
|
||||
"notion.api_key_placeholder": "Введите ключ API Notion",
|
||||
"notion.auto_split": "Автоматическое разбиение на страницы при экспорте диалога",
|
||||
@@ -746,11 +774,22 @@
|
||||
"notion.split_size_help": "Рекомендуется 90 для пользователей бесплатной версии Notion, 24990 для пользователей премиум-версии, значение по умолчанию — 90",
|
||||
"notion.split_size_placeholder": "Введите ограничение количества блоков на странице (по умолчанию 90)",
|
||||
"notion.title": "Настройки Notion",
|
||||
"obsidian": {
|
||||
"folder": "Папка",
|
||||
"folder_placeholder": "Пожалуйста, введите имя папки",
|
||||
"tags": "Глобальные Теги",
|
||||
"tags_placeholder": "Пожалуйста, введите имена тегов. Разделяйте несколько тегов запятыми на английском языке. В Obsidian нельзя использовать только цифры.",
|
||||
"title": "Конфигурация Obsidian",
|
||||
"vault": "Хранилище",
|
||||
"vault_placeholder": "Пожалуйста, введите имя хранилища"
|
||||
},
|
||||
"title": "Настройки данных",
|
||||
"webdav": {
|
||||
"autoSync": "Автоматическое резервное копирование",
|
||||
"autoSync.off": "Выключено",
|
||||
"backup.button": "Резервное копирование на WebDAV",
|
||||
"backup.modal.filename.placeholder": "Введите имя файла резервной копии",
|
||||
"backup.modal.title": "Резервное копирование на WebDAV",
|
||||
"host": "Хост WebDAV",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} час",
|
||||
@@ -763,18 +802,16 @@
|
||||
"path": "Путь WebDAV",
|
||||
"path.placeholder": "/backup",
|
||||
"restore.button": "Восстановление с WebDAV",
|
||||
"restore.confirm.content": "Восстановление с WebDAV перезапишет текущие данные, продолжить?",
|
||||
"restore.confirm.title": "Подтверждение восстановления",
|
||||
"restore.content": "Восстановление с WebDAV перезапишет текущие данные, продолжить?",
|
||||
"restore.modal.select.placeholder": "Выберите файл резервной копии для восстановления",
|
||||
"restore.modal.title": "Восстановление с WebDAV",
|
||||
"restore.title": "Восстановление с WebDAV",
|
||||
"syncError": "Ошибка резервного копирования",
|
||||
"syncStatus": "Статус резервного копирования",
|
||||
"title": "WebDAV",
|
||||
"user": "Пользователь WebDAV",
|
||||
"backup.modal.title": "Резервное копирование на WebDAV",
|
||||
"backup.modal.filename.placeholder": "Введите имя файла резервной копии",
|
||||
"restore.modal.title": "Восстановление с WebDAV",
|
||||
"restore.modal.select.placeholder": "Выберите файл резервной копии для восстановления",
|
||||
"restore.confirm.title": "Подтверждение восстановления",
|
||||
"restore.confirm.content": "Восстановление с WebDAV перезапишет текущие данные, продолжить?"
|
||||
"user": "Пользователь WebDAV"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -790,36 +827,6 @@
|
||||
"title": "Настройка Yuque",
|
||||
"token": "Токен Yuque",
|
||||
"token_placeholder": "Введите токен Yuque"
|
||||
},
|
||||
"obsidian": {
|
||||
"check": {
|
||||
"button": "Проверить",
|
||||
"empty_url": "Сначала введите URL REST API Obsidian",
|
||||
"empty_api_key": "Сначала введите API Key Obsidian",
|
||||
"fail": "Не удалось проверить подключение к Obsidian",
|
||||
"success": "Подключение к Obsidian успешно проверено"
|
||||
},
|
||||
"help": "Сначала установите плагин Local REST API Obsidian, затем получите API Key Obsidian",
|
||||
"url": "URL базы знаний Obsidian",
|
||||
"url_placeholder": "http://127.0.0.1:27123/",
|
||||
"title": "Настройка Obsidian",
|
||||
"api_key": "API Key Obsidian",
|
||||
"api_key_placeholder": "Введите API Key Obsidian"
|
||||
},
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "Проверить",
|
||||
"empty_url": "Сначала введите URL Joplin",
|
||||
"empty_token": "Сначала введите токен Joplin",
|
||||
"fail": "Не удалось проверить подключение к Joplin",
|
||||
"success": "Подключение к Joplin успешно проверено"
|
||||
},
|
||||
"title": "Настройка Joplin",
|
||||
"help": "Включите Joplin опцию, проверьте порт и скопируйте токен",
|
||||
"url": "URL Joplin",
|
||||
"url_placeholder": "http://127.0.0.1:41184/",
|
||||
"token": "Токен Joplin",
|
||||
"token_placeholder": "Введите токен Joplin"
|
||||
}
|
||||
},
|
||||
"display.assistant.title": "Настройки ассистентов",
|
||||
@@ -865,12 +872,15 @@
|
||||
"input.target_language.english": "Английский",
|
||||
"input.target_language.japanese": "Японский",
|
||||
"input.target_language.russian": "Русский",
|
||||
"launch.onboot": "Автозапуск при включении",
|
||||
"launch.title": "Запуск",
|
||||
"launch.totray": "Свернуть в трей при запуске",
|
||||
"mcp": {
|
||||
"actions": "Действия",
|
||||
"active": "Активен",
|
||||
"addError": "Ошибка добавления сервера",
|
||||
"addServer": "Добавить сервер",
|
||||
"addSuccess": "Сервер успешно добавлен",
|
||||
"addError": "Ошибка добавления сервера",
|
||||
"args": "Аргументы",
|
||||
"argsTooltip": "Каждый аргумент с новой строки",
|
||||
"baseUrlTooltip": "Адрес удаленного URL",
|
||||
@@ -881,55 +891,51 @@
|
||||
"confirmDeleteMessage": "Вы уверены, что хотите удалить этот сервер?",
|
||||
"deleteError": "Не удалось удалить сервер",
|
||||
"deleteSuccess": "Сервер успешно удален",
|
||||
"dependenciesInstall": "Установить зависимости",
|
||||
"dependenciesInstalling": "Установка зависимостей...",
|
||||
"description": "Описание",
|
||||
"duplicateName": "Сервер с таким именем уже существует",
|
||||
"editJson": "Редактировать JSON",
|
||||
"editServer": "Редактировать сервер",
|
||||
"env": "Переменные окружения",
|
||||
"envTooltip": "Формат: KEY=value, по одной на строку",
|
||||
"findMore": "Найти больше MCP серверов",
|
||||
"install": "Установить",
|
||||
"installError": "Не удалось установить зависимости",
|
||||
"installSuccess": "Зависимости успешно установлены",
|
||||
"jsonFormatError": "Ошибка форматирования JSON",
|
||||
"jsonModeHint": "Редактируйте JSON-форматирование конфигурации сервера MCP. Перед сохранением убедитесь, что формат правильный.",
|
||||
"jsonSaveError": "Не удалось сохранить конфигурацию JSON",
|
||||
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
||||
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
||||
"name": "Имя",
|
||||
"nameRequired": "Пожалуйста, введите имя сервера",
|
||||
"noServers": "Серверы не настроены",
|
||||
"npx_list": {
|
||||
"actions": "Действия",
|
||||
"desc": "Поиск и добавление npm пакетов в качестве MCP серверов",
|
||||
"description": "Описание",
|
||||
"no_packages": "Ничего не найдено",
|
||||
"npm": "NPM",
|
||||
"package_name": "Имя пакета",
|
||||
"scope_placeholder": "Введите область npm (например, @your-org)",
|
||||
"scope_required": "Пожалуйста, введите область npm",
|
||||
"search": "Поиск",
|
||||
"search_error": "Ошибка поиска",
|
||||
"title": "Список пакетов NPX",
|
||||
"usage": "Использование",
|
||||
"version": "Версия"
|
||||
},
|
||||
"serverPlural": "серверы",
|
||||
"serverSingular": "сервер",
|
||||
"title": "Серверы MCP",
|
||||
"type": "Тип",
|
||||
"updateSuccess": "Сервер успешно обновлен",
|
||||
"updateError": "Ошибка обновления сервера",
|
||||
"url": "URL",
|
||||
"toggleError": "Переключение не удалось",
|
||||
"dependenciesInstalling": "Установка зависимостей...",
|
||||
"dependenciesInstall": "Установить зависимости",
|
||||
"installSuccess": "Зависимости успешно установлены",
|
||||
"installError": "Не удалось установить зависимости",
|
||||
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
||||
"install": "Установить",
|
||||
"npx_list": {
|
||||
"title": "Список пакетов NPX",
|
||||
"desc": "Поиск и добавление npm пакетов в качестве MCP серверов",
|
||||
"scope_placeholder": "Введите область npm (например, @your-org)",
|
||||
"search": "Поиск",
|
||||
"package_name": "Имя пакета",
|
||||
"description": "Описание",
|
||||
"usage": "Использование",
|
||||
"npm": "NPM",
|
||||
"version": "Версия",
|
||||
"actions": "Действия",
|
||||
"scope_required": "Пожалуйста, введите область npm",
|
||||
"no_packages": "Ничего не найдено",
|
||||
"search_error": "Ошибка поиска"
|
||||
},
|
||||
"editJson": "Редактировать JSON",
|
||||
"jsonModeHint": "Редактируйте JSON-форматирование конфигурации сервера MCP. Перед сохранением убедитесь, что формат правильный.",
|
||||
"jsonFormatError": "Ошибка форматирования JSON",
|
||||
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
||||
"jsonSaveError": "Не удалось сохранить конфигурацию JSON"
|
||||
"type": "Тип",
|
||||
"updateError": "Ошибка обновления сервера",
|
||||
"updateSuccess": "Сервер успешно обновлен",
|
||||
"url": "URL"
|
||||
},
|
||||
"messages.divider": "Показывать разделитель между сообщениями",
|
||||
"messages.navigation": "Навигация сообщений",
|
||||
"messages.navigation.none": "Не показывать",
|
||||
"messages.navigation.buttons": "Кнопки пагинации",
|
||||
"messages.navigation.anchor": "Диалог анкор",
|
||||
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
|
||||
"messages.grid_popover_trigger.click": "Нажатие для отображения",
|
||||
@@ -943,6 +949,10 @@
|
||||
"messages.math_engine": "Математический движок",
|
||||
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
|
||||
"messages.model.title": "Настройки модели",
|
||||
"messages.navigation": "Навигация сообщений",
|
||||
"messages.navigation.anchor": "Диалог анкор",
|
||||
"messages.navigation.buttons": "Кнопки пагинации",
|
||||
"messages.navigation.none": "Не показывать",
|
||||
"messages.title": "Настройки сообщений",
|
||||
"messages.use_serif_font": "Использовать serif шрифт",
|
||||
"model": "Модель по умолчанию",
|
||||
@@ -1005,43 +1015,43 @@
|
||||
"check": "Проверить",
|
||||
"check_all_keys": "Проверить все ключи",
|
||||
"check_multiple_keys": "Проверить несколько ключей API",
|
||||
"copilot": {
|
||||
"auth_failed": "Github Copilot认证失败",
|
||||
"auth_success": "Github Copilot认证成功",
|
||||
"auth_success_title": "Аутентификация успешна",
|
||||
"code_failed": "Получение кода устройства не удалось, пожалуйста, попробуйте еще раз.",
|
||||
"code_generated_desc": "Пожалуйста, скопируйте код устройства в приведенную ниже ссылку браузера.",
|
||||
"code_generated_title": "Получить код устройства",
|
||||
"confirm_login": "Чрезмерное использование может привести к блокировке вашего Github, будьте осторожны!!!!",
|
||||
"confirm_title": "Предупреждение о рисках",
|
||||
"connect": "Подключить Github",
|
||||
"custom_headers": "Пользовательские заголовки запроса",
|
||||
"description": "Ваша учетная запись Github должна подписаться на Copilot.",
|
||||
"expand": "развернуть",
|
||||
"headers_description": "Пользовательские заголовки запроса (формат json)",
|
||||
"invalid_json": "Ошибка формата JSON",
|
||||
"login": "Войти в Github",
|
||||
"logout": "Выйти из Github",
|
||||
"logout_failed": "Не удалось выйти, пожалуйста, повторите попытку.",
|
||||
"logout_success": "Успешно вышел",
|
||||
"model_setting": "Настройки модели",
|
||||
"open_verification_first": "Пожалуйста, сначала щелкните по ссылке выше, чтобы перейти на страницу проверки.",
|
||||
"rate_limit": "Ограничение скорости",
|
||||
"tooltip": "Для использования Github Copilot необходимо сначала войти в Github."
|
||||
},
|
||||
"delete.content": "Вы уверены, что хотите удалить этот провайдер?",
|
||||
"delete.title": "Удалить провайдер",
|
||||
"docs_check": "Проверить",
|
||||
"docs_more_details": "для получения дополнительной информации",
|
||||
"get_api_key": "Получить ключ API",
|
||||
"is_not_support_array_content": "Включить совместимый режим",
|
||||
"no_models": "Пожалуйста, добавьте модели перед проверкой соединения с API",
|
||||
"not_checked": "Не проверено",
|
||||
"remove_duplicate_keys": "Удалить дубликаты ключей",
|
||||
"remove_invalid_keys": "Удалить недействительные ключи",
|
||||
"search": "Поиск поставщиков...",
|
||||
"search_placeholder": "Поиск по ID или имени модели",
|
||||
"title": "Провайдеры моделей",
|
||||
"is_not_support_array_content": "Включить совместимый режим",
|
||||
"copilot": {
|
||||
"tooltip": "Для использования Github Copilot необходимо сначала войти в Github.",
|
||||
"description": "Ваша учетная запись Github должна подписаться на Copilot.",
|
||||
"login": "Войти в Github",
|
||||
"connect": "Подключить Github",
|
||||
"logout": "Выйти из Github",
|
||||
"auth_success_title": "Аутентификация успешна",
|
||||
"code_generated_title": "Получить код устройства",
|
||||
"code_generated_desc": "Пожалуйста, скопируйте код устройства в приведенную ниже ссылку браузера.",
|
||||
"code_failed": "Получение кода устройства не удалось, пожалуйста, попробуйте еще раз.",
|
||||
"auth_success": "Github Copilot认证成功",
|
||||
"auth_failed": "Github Copilot认证失败",
|
||||
"logout_success": "Успешно вышел",
|
||||
"logout_failed": "Не удалось выйти, пожалуйста, повторите попытку.",
|
||||
"confirm_title": "Предупреждение о рисках",
|
||||
"confirm_login": "Чрезмерное использование может привести к блокировке вашего Github, будьте осторожны!!!!",
|
||||
"rate_limit": "Ограничение скорости",
|
||||
"custom_headers": "Пользовательские заголовки запроса",
|
||||
"headers_description": "Пользовательские заголовки запроса (формат json)",
|
||||
"expand": "развернуть",
|
||||
"model_setting": "Настройки модели",
|
||||
"invalid_json": "Ошибка формата JSON",
|
||||
"open_verification_first": "Пожалуйста, сначала щелкните по ссылке выше, чтобы перейти на страницу проверки."
|
||||
}
|
||||
"title": "Провайдеры моделей"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1056,9 +1066,9 @@
|
||||
"quickAssistant": {
|
||||
"click_tray_to_show": "Нажмите на иконку трея для запуска",
|
||||
"enable_quick_assistant": "Включить быстрый помощник",
|
||||
"read_clipboard_at_startup": "Чтение буфера обмена при запуске",
|
||||
"title": "Быстрый помощник",
|
||||
"use_shortcut_to_show": "Нажмите на иконку трея или используйте горячие клавиши для запуска",
|
||||
"read_clipboard_at_startup": "Чтение буфера обмена при запуске"
|
||||
"use_shortcut_to_show": "Нажмите на иконку трея или используйте горячие клавиши для запуска"
|
||||
},
|
||||
"shortcuts": {
|
||||
"action": "Действие",
|
||||
@@ -1095,14 +1105,18 @@
|
||||
"topic.position.left": "Слева",
|
||||
"topic.position.right": "Справа",
|
||||
"topic.show.time": "Показывать время топика",
|
||||
"tray.title": "Включить значок системного трея",
|
||||
"tray.onclose": "Свернуть в трей при закрытии",
|
||||
"tray.show": "Показать значок в трее",
|
||||
"tray.title": "Трей",
|
||||
"websearch": {
|
||||
"blacklist": "Черный список",
|
||||
"blacklist_description": "Результаты из следующих веб-сайтов не будут отображаться в результатах поиска",
|
||||
"blacklist_tooltip": "Пожалуйста, используйте следующий формат (разделенный переносами строк)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
"check": "проверка",
|
||||
"check_success": "Проверка успешна",
|
||||
"check_failed": "Проверка не прошла",
|
||||
"check_success": "Проверка успешна",
|
||||
"enhance_mode": "Режим улучшения поиска",
|
||||
"enhance_mode_tooltip": "Используйте модель по умолчанию для извлечения ключевых слов из проблемы и поиска",
|
||||
"get_api_key": "Получить ключ API",
|
||||
"no_provider_selected": "Пожалуйста, выберите поставщика поисковых услуг, затем проверьте.",
|
||||
"search_max_result": "Количество результатов поиска",
|
||||
@@ -1110,8 +1124,6 @@
|
||||
"search_provider_placeholder": "Выберите поставщика поисковых услуг",
|
||||
"search_result_default": "По умолчанию",
|
||||
"search_with_time": "Поиск, содержащий дату",
|
||||
"enhance_mode": "Режим улучшения поиска",
|
||||
"enhance_mode_tooltip": "Используйте модель по умолчанию для извлечения ключевых слов из проблемы и поиска",
|
||||
"tavily": {
|
||||
"api_key": "Ключ API Tavily",
|
||||
"api_key.placeholder": "Введите ключ API Tavily",
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
"input.estimated_tokens.tip": "预估 token 数",
|
||||
"input.expand": "展开",
|
||||
"input.file_not_supported": "模型不支持此文件类型",
|
||||
"input.generate_image": "生成图片",
|
||||
"input.generate_image_not_supported": "模型不支持生成图片",
|
||||
"input.knowledge_base": "知识库",
|
||||
"input.new.context": "清除上下文 {{Command}}",
|
||||
"input.new_topic": "新话题 {{Command}}",
|
||||
@@ -126,6 +128,12 @@
|
||||
"message.quote": "引用",
|
||||
"message.regenerate.model": "切换模型",
|
||||
"message.useful": "有用",
|
||||
"navigation": {
|
||||
"first": "已经是第一条消息",
|
||||
"last": "已经是最后一条消息",
|
||||
"next": "下一条消息",
|
||||
"prev": "上一条消息"
|
||||
},
|
||||
"resend": "重新发送",
|
||||
"save": "保存",
|
||||
"settings.code_collapsible": "代码块可折叠",
|
||||
@@ -157,37 +165,42 @@
|
||||
"topics.edit.placeholder": "输入新名称",
|
||||
"topics.edit.title": "编辑话题名",
|
||||
"topics.export.image": "导出为图片",
|
||||
"topics.export.joplin": "导出到 Joplin",
|
||||
"topics.export.md": "导出为 Markdown",
|
||||
"topics.export.notion": "导出到 Notion",
|
||||
"topics.export.obsidian": "导出到 Obsidian",
|
||||
"topics.export.obsidian_atributes": "配置笔记属性",
|
||||
"topics.export.obsidian_btn": "确定",
|
||||
"topics.export.obsidian_created": "创建时间",
|
||||
"topics.export.obsidian_created_placeholder": "请选择创建时间",
|
||||
"topics.export.obsidian_export_failed": "导出失败",
|
||||
"topics.export.obsidian_export_success": "导出成功",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
||||
"topics.export.obsidian_operate": "处理方式",
|
||||
"topics.export.obsidian_operate_append": "追加",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆盖)",
|
||||
"topics.export.obsidian_operate_placeholder": "请选择处理方式",
|
||||
"topics.export.obsidian_operate_prepend": "前置",
|
||||
"topics.export.obsidian_source": "来源",
|
||||
"topics.export.obsidian_source_placeholder": "请输入来源",
|
||||
"topics.export.obsidian_tags": "标签",
|
||||
"topics.export.obsidian_tags_placeholder": "请输入标签,多个标签用英文逗号分隔",
|
||||
"topics.export.obsidian_title": "标题",
|
||||
"topics.export.obsidian_title_placeholder": "请输入标题",
|
||||
"topics.export.obsidian_title_required": "标题不能为空",
|
||||
"topics.export.title": "导出",
|
||||
"topics.export.word": "导出为 Word",
|
||||
"topics.export.yuque": "导出到语雀",
|
||||
"topics.export.obsidian": "导出到 Obsidian",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
||||
"topics.export.obsidian_fetch_failed": "获取 Obsidian 文件夹结构失败",
|
||||
"topics.export.obsidian_select_folder": "选择 Obsidian 文件夹",
|
||||
"topics.export.obsidian_select_folder.btn": "确定",
|
||||
"topics.export.obsidian_export_success": "导出成功",
|
||||
"topics.export.obsidian_export_failed": "导出失败",
|
||||
"topics.export.obsidian_show_md_files": "显示md文件",
|
||||
"topics.export.obsidian_selected_path": "已选择路径",
|
||||
"topics.export.joplin": "导出到 Joplin",
|
||||
"topics.list": "话题列表",
|
||||
"topics.move_to": "移动到",
|
||||
"topics.new": "开始新对话",
|
||||
"topics.pinned": "固定话题",
|
||||
"topics.prompt": "话题提示词",
|
||||
"topics.prompt.edit.title": "编辑话题提示词",
|
||||
"topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词",
|
||||
"topics.title": "话题",
|
||||
"topics.unpinned": "取消固定",
|
||||
"topics.new": "开始新对话",
|
||||
"translate": "翻译",
|
||||
"navigation": {
|
||||
"prev": "上一条消息",
|
||||
"next": "下一条消息",
|
||||
"first": "已经是第一条消息",
|
||||
"last": "已经是最后一条消息"
|
||||
}
|
||||
"translate": "翻译"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "收起",
|
||||
@@ -197,6 +210,7 @@
|
||||
},
|
||||
"common": {
|
||||
"add": "添加",
|
||||
"advanced_settings": "高级设置",
|
||||
"and": "和",
|
||||
"assistant": "智能体",
|
||||
"avatar": "头像",
|
||||
@@ -206,8 +220,8 @@
|
||||
"clear": "清除",
|
||||
"close": "关闭",
|
||||
"confirm": "确认",
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"copy": "复制",
|
||||
"cut": "剪切",
|
||||
"default": "默认",
|
||||
"delete": "删除",
|
||||
@@ -216,6 +230,7 @@
|
||||
"download": "下载",
|
||||
"duplicate": "复制",
|
||||
"edit": "编辑",
|
||||
"expand": "展开",
|
||||
"footnote": "引用内容",
|
||||
"footnotes": "引用内容",
|
||||
"fullscreen": "已进入全屏模式,按 F11 退出",
|
||||
@@ -223,6 +238,7 @@
|
||||
"language": "语言",
|
||||
"model": "模型",
|
||||
"models": "模型",
|
||||
"more": "更多",
|
||||
"name": "名称",
|
||||
"paste": "粘贴",
|
||||
"prompt": "提示词",
|
||||
@@ -235,10 +251,7 @@
|
||||
"select": "选择",
|
||||
"topics": "话题",
|
||||
"warning": "警告",
|
||||
"you": "用户",
|
||||
"more": "更多",
|
||||
"advanced_settings": "高级设置",
|
||||
"expand": "展开"
|
||||
"you": "用户"
|
||||
},
|
||||
"docs": {
|
||||
"title": "帮助文档"
|
||||
@@ -296,6 +309,12 @@
|
||||
"title": "文件",
|
||||
"type": "类型"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "模型在内存中保持的时间(默认:5分钟)",
|
||||
"keep_alive_time.placeholder": "分钟",
|
||||
"keep_alive_time.title": "保持活跃时间",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "继续聊天",
|
||||
"locate.message": "定位到消息",
|
||||
@@ -363,11 +382,11 @@
|
||||
"threshold_placeholder": "未设置",
|
||||
"threshold_too_large_or_small": "阈值不能大于1或小于0",
|
||||
"threshold_tooltip": "用于衡量用户问题与知识库内容之间的相关性(0-1)",
|
||||
"topN": "返回结果数量",
|
||||
"topN_placeholder": "未设置",
|
||||
"topN__too_large_or_small": "返回结果数量不能大于100或小于1",
|
||||
"topN_tooltip": "返回的匹配结果数量,数值越大,匹配结果越多,但消耗的 Token 也越多",
|
||||
"title": "知识库",
|
||||
"topN": "返回结果数量",
|
||||
"topN__too_large_or_small": "返回结果数量不能大于100或小于1",
|
||||
"topN_placeholder": "未设置",
|
||||
"topN_tooltip": "返回的匹配结果数量,数值越大,匹配结果越多,但消耗的 Token 也越多",
|
||||
"url_added": "网址已添加",
|
||||
"url_placeholder": "请输入网址, 多个网址用回车分隔",
|
||||
"urls": "网址",
|
||||
@@ -419,22 +438,22 @@
|
||||
"title": "Mermaid 图表"
|
||||
},
|
||||
"message": {
|
||||
"attachments": {
|
||||
"pasted_text": "剪切板文件",
|
||||
"pasted_image": "剪切板图片"
|
||||
},
|
||||
"api.check.model.title": "请选择要检测的模型",
|
||||
"api.connection.failed": "连接失败",
|
||||
"api.connection.success": "连接成功",
|
||||
"assistant.added.content": "智能体添加成功",
|
||||
"attachments": {
|
||||
"pasted_image": "剪切板图片",
|
||||
"pasted_text": "剪切板文件"
|
||||
},
|
||||
"backup.failed": "备份失败",
|
||||
"backup.start.success": "开始备份",
|
||||
"backup.success": "备份成功",
|
||||
"chat.completion.paused": "会话已停止",
|
||||
"citations": "引用内容",
|
||||
"copied": "已复制",
|
||||
"copy.success": "复制成功",
|
||||
"copy.failed": "复制失败",
|
||||
"copy.success": "复制成功",
|
||||
"error.chunk_overlap_too_large": "分段重叠不能大于分段大小",
|
||||
"error.dimension_too_large": "内容尺寸过大",
|
||||
"error.enter.api.host": "请输入您的 API 地址",
|
||||
@@ -447,20 +466,20 @@
|
||||
"error.invalid.enter.model": "请选择一个模型",
|
||||
"error.invalid.proxy.url": "无效的代理地址",
|
||||
"error.invalid.webdav": "无效的 WebDAV 设置",
|
||||
"error.joplin.export": "导出 Joplin 失败,请保持 Joplin 已运行并检查连接状态或检查配置",
|
||||
"error.joplin.no_config": "未配置 Joplin 授权令牌 或 URL",
|
||||
"error.markdown.export.preconf": "导出Markdown文件到预先设定的路径失败",
|
||||
"error.markdown.export.specified": "导出Markdown文件失败",
|
||||
"error.notion.export": "导出 Notion 错误,请检查连接状态并对照文档检查配置",
|
||||
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
|
||||
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
|
||||
"error.yuque.no_config": "未配置语雀 Token 或 知识库 URL",
|
||||
"error.joplin.no_config": "未配置 Joplin 授权令牌 或 URL",
|
||||
"error.joplin.export": "导出 Joplin 失败,请保持 Joplin 已运行并检查连接状态或检查配置",
|
||||
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
||||
"group.delete.title": "删除分组消息",
|
||||
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
||||
"info.notion.block_reach_limit": "对话过长,正在分页导出到Notion",
|
||||
"loading.notion.preparing": "正在准备导出到Notion...",
|
||||
"loading.notion.exporting_progress": "正在导出到Notion ({{current}}/{{total}})...",
|
||||
"loading.notion.preparing": "正在准备导出到Notion...",
|
||||
"mention.title": "切换模型回答",
|
||||
"message.code_style": "代码风格",
|
||||
"message.delete.content": "确定要删除此消息吗?",
|
||||
@@ -483,22 +502,22 @@
|
||||
"restore.success": "恢复成功",
|
||||
"save.success.title": "保存成功",
|
||||
"searching": "正在联网搜索...",
|
||||
"success.joplin.export": "成功导出到 Joplin",
|
||||
"success.markdown.export.preconf": "成功导出 Markdown 文件到预先设定的路径",
|
||||
"success.markdown.export.specified": "成功导出 Markdown 文件",
|
||||
"success.notion.export": "成功导出到 Notion",
|
||||
"success.yuque.export": "成功导出到语雀",
|
||||
"success.joplin.export": "成功导出到 Joplin",
|
||||
"switch.disabled": "请等待当前回复完成后操作",
|
||||
"tools": {
|
||||
"completed": "已完成",
|
||||
"invoking": "调用中"
|
||||
},
|
||||
"topic.added": "话题添加成功",
|
||||
"upgrade.success.button": "重启",
|
||||
"upgrade.success.content": "重启用以完成升级",
|
||||
"upgrade.success.title": "升级成功",
|
||||
"warn.notion.exporting": "正在导出到 Notion, 请勿重复请求导出!",
|
||||
"warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试",
|
||||
"tools": {
|
||||
"invoking": "调用中",
|
||||
"completed": "已完成"
|
||||
}
|
||||
"warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "添加到侧边栏",
|
||||
@@ -537,9 +556,7 @@
|
||||
"embedding": "嵌入",
|
||||
"embedding_model": "嵌入模型",
|
||||
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"rerank_model": "重排序模型",
|
||||
"rerank_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"free": "免费",
|
||||
"function_calling": "函数调用",
|
||||
"no_matches": "无可用模型",
|
||||
"parameter_name": "参数名称",
|
||||
"parameter_type": {
|
||||
@@ -549,20 +566,22 @@
|
||||
"string": "文本"
|
||||
},
|
||||
"pinned": "已固定",
|
||||
"reasoning": "推理",
|
||||
"rerank_model": "重排模型",
|
||||
"rerank_model_support_provider": "目前重排序模型仅支持部分服务商 ({{provider}})",
|
||||
"rerank_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"search": "搜索模型...",
|
||||
"stream_output": "流式输出",
|
||||
"function_calling": "函数调用",
|
||||
"type": {
|
||||
"embedding": "嵌入",
|
||||
"free": "免费",
|
||||
"function_calling": "工具",
|
||||
"reasoning": "推理",
|
||||
"rerank": "重排",
|
||||
"select": "选择模型类型",
|
||||
"text": "文本",
|
||||
"vision": "图像",
|
||||
"function_calling": "函数调用"
|
||||
},
|
||||
"vision": "视觉",
|
||||
"websearch": "联网"
|
||||
"vision": "视觉",
|
||||
"websearch": "联网"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "伸缩对话框",
|
||||
@@ -615,10 +634,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"alayanew": "Alaya NeW",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "百川",
|
||||
"baidu-cloud": "百度云千帆",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "阿里云百炼",
|
||||
"deepseek": "深度求索",
|
||||
"dmxapi": "DMXAPI",
|
||||
@@ -627,6 +648,7 @@
|
||||
"gemini": "Gemini",
|
||||
"gitee-ai": "Gitee AI",
|
||||
"github": "GitHub Models",
|
||||
"gpustack": "GPUStack",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
@@ -655,10 +677,7 @@
|
||||
"xirang": "天翼云息壤",
|
||||
"yi": "零一万物",
|
||||
"zhinao": "360智脑",
|
||||
"zhipu": "智谱AI",
|
||||
"copilot": "GitHub Copilot",
|
||||
"gpustack": "GPUStack",
|
||||
"alayanew": "Alaya NeW"
|
||||
"zhipu": "智谱AI"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "确定要恢复数据吗?",
|
||||
@@ -674,12 +693,6 @@
|
||||
},
|
||||
"title": "数据恢复"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "模型在内存中保持的时间(默认:5分钟)",
|
||||
"keep_alive_time.placeholder": "分钟",
|
||||
"keep_alive_time.title": "保持活跃时间",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"settings": {
|
||||
"about": "关于我们",
|
||||
"about.checkingUpdate": "正在检查更新...",
|
||||
@@ -726,15 +739,30 @@
|
||||
"data.title": "数据目录",
|
||||
"hour_interval_one": "{{count}} 小时",
|
||||
"hour_interval_other": "{{count}} 小时",
|
||||
"minute_interval_one": "{{count}} 分钟",
|
||||
"minute_interval_other": "{{count}} 分钟",
|
||||
"markdown_export.title": "Markdown 导出",
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "检查",
|
||||
"empty_token": "请先输入 Joplin 授权令牌",
|
||||
"empty_url": "请先输入 Joplin 剪裁服务监听 URL",
|
||||
"fail": "Joplin 连接验证失败",
|
||||
"success": "Joplin 连接验证成功"
|
||||
},
|
||||
"help": "在 Joplin 选项中,启用网页剪裁服务(无需安装浏览器插件),确认端口号,并复制授权令牌",
|
||||
"title": "Joplin 配置",
|
||||
"token": "Joplin 授权令牌",
|
||||
"token_placeholder": "请输入 Joplin 授权令牌",
|
||||
"url": "Joplin 剪裁服务监听 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "开启后,导出Markdown时会将强制使用$$来标记LaTeX公式。注意:该项也会影响所有通过Markdown导出的方式,如Notion、语雀等。",
|
||||
"markdown_export.force_dollar_math.title": "强制使用$$来标记LaTeX公式",
|
||||
"markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框",
|
||||
"markdown_export.path": "默认导出路径",
|
||||
"markdown_export.path_placeholder": "导出路径",
|
||||
"markdown_export.select": "选择",
|
||||
"markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框",
|
||||
"markdown_export.force_dollar_math.title": "强制使用$$来标记LaTeX公式",
|
||||
"markdown_export.force_dollar_math.help": "开启后,导出Markdown时会将强制使用$$来标记LaTeX公式。注意:该项也会影响所有通过Markdown导出的方式,如Notion、语雀等。",
|
||||
"markdown_export.title": "Markdown 导出",
|
||||
"minute_interval_one": "{{count}} 分钟",
|
||||
"minute_interval_other": "{{count}} 分钟",
|
||||
"notion.api_key": "Notion 密钥",
|
||||
"notion.api_key_placeholder": "请输入Notion 密钥",
|
||||
"notion.auto_split": "导出对话时自动分页",
|
||||
@@ -756,11 +784,22 @@
|
||||
"notion.split_size_help": "Notion免费版用户建议设置为90,高级版用户建议设置为24990,默认值为90",
|
||||
"notion.split_size_placeholder": "请输入每页块数限制(默认90)",
|
||||
"notion.title": "Notion 配置",
|
||||
"obsidian": {
|
||||
"folder": "文件夹",
|
||||
"folder_placeholder": "请输入文件夹名称",
|
||||
"tags": "全局标签",
|
||||
"tags_placeholder": "请输入标签名称, 多个标签用英文逗号分隔",
|
||||
"title": "Obsidian 配置",
|
||||
"vault": "保管库",
|
||||
"vault_placeholder": "请输入保管库名称"
|
||||
},
|
||||
"title": "数据设置",
|
||||
"webdav": {
|
||||
"autoSync": "自动备份",
|
||||
"autoSync.off": "关闭",
|
||||
"backup.button": "备份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "请输入备份文件名",
|
||||
"backup.modal.title": "备份到 WebDAV",
|
||||
"host": "WebDAV 地址",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 小时",
|
||||
@@ -772,14 +811,12 @@
|
||||
"password": "WebDAV 密码",
|
||||
"path": "WebDAV 路径",
|
||||
"path.placeholder": "/backup",
|
||||
"backup.modal.title": "备份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "请输入备份文件名",
|
||||
"restore.modal.title": "从 WebDAV 恢复",
|
||||
"restore.modal.select.placeholder": "请选择要恢复的备份文件",
|
||||
"restore.confirm.title": "确认恢复",
|
||||
"restore.confirm.content": "从 WebDAV 恢复将会覆盖当前数据,是否继续?",
|
||||
"restore.button": "从 WebDAV 恢复",
|
||||
"restore.confirm.content": "从 WebDAV 恢复将会覆盖当前数据,是否继续?",
|
||||
"restore.confirm.title": "确认恢复",
|
||||
"restore.content": "从 WebDAV 恢复将覆盖当前数据,是否继续?",
|
||||
"restore.modal.select.placeholder": "请选择要恢复的备份文件",
|
||||
"restore.modal.title": "从 WebDAV 恢复",
|
||||
"restore.title": "从 WebDAV 恢复",
|
||||
"syncError": "备份错误",
|
||||
"syncStatus": "备份状态",
|
||||
@@ -800,36 +837,6 @@
|
||||
"title": "语雀配置",
|
||||
"token": "语雀 Token",
|
||||
"token_placeholder": "请输入语雀Token"
|
||||
},
|
||||
"obsidian": {
|
||||
"check": {
|
||||
"button": "检查",
|
||||
"empty_url": "请先输入 Obsidian REST API URL",
|
||||
"empty_api_key": "请先输入 Obsidian API Key",
|
||||
"fail": "Obsidian 连接验证失败",
|
||||
"success": "Obsidian 连接验证成功"
|
||||
},
|
||||
"help": "先安装 Obsidian 插件 Local REST API,然后获取 Obsidian API Key",
|
||||
"url": "Obsidian 知识库 URL",
|
||||
"url_placeholder": "http://127.0.0.1:27123/",
|
||||
"title": "Obsidian 配置",
|
||||
"api_key": "Obsidian API Key",
|
||||
"api_key_placeholder": "请输入 Obsidian API Key"
|
||||
},
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "检查",
|
||||
"empty_url": "请先输入 Joplin 剪裁服务监听 URL",
|
||||
"empty_token": "请先输入 Joplin 授权令牌",
|
||||
"fail": "Joplin 连接验证失败",
|
||||
"success": "Joplin 连接验证成功"
|
||||
},
|
||||
"title": "Joplin 配置",
|
||||
"help": "在 Joplin 选项中,启用网页剪裁服务(无需安装浏览器插件),确认端口号,并复制授权令牌",
|
||||
"url": "Joplin 剪裁服务监听 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/",
|
||||
"token": "Joplin 授权令牌",
|
||||
"token_placeholder": "请输入 Joplin 授权令牌"
|
||||
}
|
||||
},
|
||||
"display.assistant.title": "助手设置",
|
||||
@@ -875,6 +882,9 @@
|
||||
"input.target_language.english": "英文",
|
||||
"input.target_language.japanese": "日文",
|
||||
"input.target_language.russian": "俄文",
|
||||
"launch.onboot": "开机自动启动",
|
||||
"launch.title": "启动",
|
||||
"launch.totray": "启动时最小化到托盘",
|
||||
"knowledge": {
|
||||
"title": "知识库",
|
||||
"ocr": {
|
||||
@@ -885,9 +895,9 @@
|
||||
"mcp": {
|
||||
"actions": "操作",
|
||||
"active": "启用",
|
||||
"addError": "添加服务器失败",
|
||||
"addServer": "添加服务器",
|
||||
"addSuccess": "服务器添加成功",
|
||||
"addError": "添加服务器失败",
|
||||
"args": "参数",
|
||||
"argsTooltip": "每个参数占一行",
|
||||
"baseUrlTooltip": "远程 URL 地址",
|
||||
@@ -898,55 +908,51 @@
|
||||
"confirmDeleteMessage": "您确定要删除该服务器吗?",
|
||||
"deleteError": "删除服务器失败",
|
||||
"deleteSuccess": "服务器删除成功",
|
||||
"dependenciesInstall": "安装依赖项",
|
||||
"dependenciesInstalling": "正在安装依赖项...",
|
||||
"description": "描述",
|
||||
"duplicateName": "已存在同名服务器",
|
||||
"editJson": "编辑JSON",
|
||||
"editServer": "编辑服务器",
|
||||
"env": "环境变量",
|
||||
"envTooltip": "格式:KEY=value,每行一个",
|
||||
"findMore": "更多 MCP 服务器",
|
||||
"install": "安装",
|
||||
"installError": "安装依赖项失败",
|
||||
"installSuccess": "依赖项安装成功",
|
||||
"jsonFormatError": "JSON格式化错误",
|
||||
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。",
|
||||
"jsonSaveError": "保存JSON配置失败",
|
||||
"jsonSaveSuccess": "JSON配置已保存",
|
||||
"missingDependencies": "缺失,请安装它以继续",
|
||||
"name": "名称",
|
||||
"nameRequired": "请输入服务器名称",
|
||||
"noServers": "未配置服务器",
|
||||
"npx_list": {
|
||||
"actions": "操作",
|
||||
"desc": "搜索并添加 npm 包作为 MCP 服务",
|
||||
"description": "描述",
|
||||
"no_packages": "未找到包",
|
||||
"npm": "NPM",
|
||||
"package_name": "包名称",
|
||||
"scope_placeholder": "输入 npm 作用域 (例如 @your-org)",
|
||||
"scope_required": "请输入 npm 作用域",
|
||||
"search": "搜索",
|
||||
"search_error": "搜索失败",
|
||||
"title": "NPX 包列表",
|
||||
"usage": "用法",
|
||||
"version": "版本"
|
||||
},
|
||||
"serverPlural": "服务器",
|
||||
"serverSingular": "服务器",
|
||||
"title": "MCP 服务器",
|
||||
"type": "类型",
|
||||
"updateSuccess": "服务器更新成功",
|
||||
"updateError": "更新服务器失败",
|
||||
"url": "URL",
|
||||
"toggleError": "切换失败",
|
||||
"dependenciesInstalling": "正在安装依赖项...",
|
||||
"dependenciesInstall": "安装依赖项",
|
||||
"installSuccess": "依赖项安装成功",
|
||||
"installError": "安装依赖项失败",
|
||||
"missingDependencies": "缺失,请安装它以继续",
|
||||
"install": "安装",
|
||||
"npx_list": {
|
||||
"title": "NPX 包列表",
|
||||
"desc": "搜索并添加 npm 包作为 MCP 服务",
|
||||
"scope_placeholder": "输入 npm 作用域 (例如 @your-org)",
|
||||
"search": "搜索",
|
||||
"package_name": "包名称",
|
||||
"description": "描述",
|
||||
"usage": "用法",
|
||||
"npm": "NPM",
|
||||
"version": "版本",
|
||||
"actions": "操作",
|
||||
"scope_required": "请输入 npm 作用域",
|
||||
"no_packages": "未找到包",
|
||||
"search_error": "搜索失败"
|
||||
},
|
||||
"editJson": "编辑JSON",
|
||||
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。",
|
||||
"jsonFormatError": "JSON格式化错误",
|
||||
"jsonSaveSuccess": "JSON配置已保存",
|
||||
"jsonSaveError": "保存JSON配置失败"
|
||||
"type": "类型",
|
||||
"updateError": "更新服务器失败",
|
||||
"updateSuccess": "服务器更新成功",
|
||||
"url": "URL"
|
||||
},
|
||||
"messages.divider": "消息分割线",
|
||||
"messages.navigation": "对话导航按钮",
|
||||
"messages.navigation.none": "不显示",
|
||||
"messages.navigation.buttons": "上下按钮",
|
||||
"messages.navigation.anchor": "对话锚点",
|
||||
"messages.grid_columns": "消息网格展示列数",
|
||||
"messages.grid_popover_trigger": "网格详情触发",
|
||||
"messages.grid_popover_trigger.click": "点击显示",
|
||||
@@ -960,6 +966,10 @@
|
||||
"messages.math_engine": "数学公式引擎",
|
||||
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||
"messages.model.title": "模型设置",
|
||||
"messages.navigation": "对话导航按钮",
|
||||
"messages.navigation.anchor": "对话锚点",
|
||||
"messages.navigation.buttons": "上下按钮",
|
||||
"messages.navigation.none": "不显示",
|
||||
"messages.title": "消息设置",
|
||||
"messages.use_serif_font": "使用衬线字体",
|
||||
"model": "默认模型",
|
||||
@@ -1022,43 +1032,43 @@
|
||||
"check": "检查",
|
||||
"check_all_keys": "检查所有密钥",
|
||||
"check_multiple_keys": "检查多个 API 密钥",
|
||||
"copilot": {
|
||||
"auth_failed": "Github Copilot 认证失败",
|
||||
"auth_success": "Github Copilot 认证成功",
|
||||
"auth_success_title": "认证成功",
|
||||
"code_failed": "获取 Device Code 失败,请重试",
|
||||
"code_generated_desc": "请将 Device Code 复制到下面的浏览器链接中",
|
||||
"code_generated_title": "获取 Device Code",
|
||||
"confirm_login": "过度使用可能会导致您的 Github 账号遭到封号,请谨慎使用!!!!",
|
||||
"confirm_title": "风险警告",
|
||||
"connect": "连接 Github",
|
||||
"custom_headers": "自定义请求头",
|
||||
"description": "您的 Github 账号需要订阅 Copilot",
|
||||
"expand": "展开",
|
||||
"headers_description": "自定义请求头(json格式)",
|
||||
"invalid_json": "JSON 格式错误",
|
||||
"login": "登录 Github",
|
||||
"logout": "退出 Github",
|
||||
"logout_failed": "退出失败,请重试",
|
||||
"logout_success": "已成功退出",
|
||||
"model_setting": "模型设置",
|
||||
"open_verification_first": "请先点击上方链接访问验证页面",
|
||||
"rate_limit": "速率限制",
|
||||
"tooltip": "使用 Github Copilot 需要先登录 Github"
|
||||
},
|
||||
"delete.content": "确定要删除此模型提供商吗?",
|
||||
"delete.title": "删除提供商",
|
||||
"docs_check": "查看",
|
||||
"docs_more_details": "获取更多详情",
|
||||
"get_api_key": "点击这里获取密钥",
|
||||
"is_not_support_array_content": "开启兼容模式",
|
||||
"no_models": "请先添加模型再检查 API 连接",
|
||||
"not_checked": "未检查",
|
||||
"remove_duplicate_keys": "移除重复密钥",
|
||||
"remove_invalid_keys": "删除无效密钥",
|
||||
"search": "搜索模型平台...",
|
||||
"search_placeholder": "搜索模型 ID 或名称",
|
||||
"title": "模型服务",
|
||||
"is_not_support_array_content": "开启兼容模式",
|
||||
"copilot": {
|
||||
"tooltip": "使用 Github Copilot 需要先登录 Github",
|
||||
"description": "您的 Github 账号需要订阅 Copilot",
|
||||
"login": "登录 Github",
|
||||
"connect": "连接 Github",
|
||||
"logout": "退出 Github",
|
||||
"auth_success_title": "认证成功",
|
||||
"code_generated_title": "获取 Device Code",
|
||||
"code_generated_desc": "请将 Device Code 复制到下面的浏览器链接中",
|
||||
"code_failed": "获取 Device Code 失败,请重试",
|
||||
"auth_success": "Github Copilot 认证成功",
|
||||
"auth_failed": "Github Copilot 认证失败",
|
||||
"logout_success": "已成功退出",
|
||||
"logout_failed": "退出失败,请重试",
|
||||
"confirm_title": "风险警告",
|
||||
"confirm_login": "过度使用可能会导致您的 Github 账号遭到封号,请谨慎使用!!!!",
|
||||
"rate_limit": "速率限制",
|
||||
"custom_headers": "自定义请求头",
|
||||
"headers_description": "自定义请求头(json格式)",
|
||||
"expand": "展开",
|
||||
"model_setting": "模型设置",
|
||||
"invalid_json": "JSON 格式错误",
|
||||
"open_verification_first": "请先点击上方链接访问验证页面"
|
||||
}
|
||||
"title": "模型服务"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1073,9 +1083,9 @@
|
||||
"quickAssistant": {
|
||||
"click_tray_to_show": "点击托盘图标启动",
|
||||
"enable_quick_assistant": "启用快捷助手",
|
||||
"read_clipboard_at_startup": "启动时读取剪贴板",
|
||||
"title": "快捷助手",
|
||||
"use_shortcut_to_show": "右键点击托盘图标或使用快捷键启动",
|
||||
"read_clipboard_at_startup": "启动时读取剪贴板"
|
||||
"use_shortcut_to_show": "右键点击托盘图标或使用快捷键启动"
|
||||
},
|
||||
"shortcuts": {
|
||||
"action": "操作",
|
||||
@@ -1112,14 +1122,18 @@
|
||||
"topic.position.left": "左侧",
|
||||
"topic.position.right": "右侧",
|
||||
"topic.show.time": "显示话题时间",
|
||||
"tray.title": "启用系统托盘图标",
|
||||
"tray.onclose": "关闭时最小化到托盘",
|
||||
"tray.show": "显示托盘图标",
|
||||
"tray.title": "托盘",
|
||||
"websearch": {
|
||||
"blacklist": "黑名单",
|
||||
"blacklist_description": "在搜索结果中不会出现以下网站的结果",
|
||||
"blacklist_tooltip": "请使用以下格式(换行分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
"check": "检查",
|
||||
"check_success": "验证成功",
|
||||
"check_failed": "验证失败",
|
||||
"check_success": "验证成功",
|
||||
"enhance_mode": "搜索增强模式",
|
||||
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
|
||||
"get_api_key": "点击这里获取密钥",
|
||||
"no_provider_selected": "请选择搜索服务商后再检查",
|
||||
"search_max_result": "搜索结果个数",
|
||||
@@ -1127,8 +1141,6 @@
|
||||
"search_provider_placeholder": "选择一个搜索服务商",
|
||||
"search_result_default": "默认",
|
||||
"search_with_time": "搜索包含日期",
|
||||
"enhance_mode": "搜索增强模式",
|
||||
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API 密钥",
|
||||
"api_key.placeholder": "请输入 Tavily API 密钥",
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
"input.estimated_tokens.tip": "預估 Token 數",
|
||||
"input.expand": "展開",
|
||||
"input.file_not_supported": "模型不支援此檔案類型",
|
||||
"input.generate_image": "生成圖片",
|
||||
"input.generate_image_not_supported": "模型不支援生成圖片",
|
||||
"input.knowledge_base": "知識庫",
|
||||
"input.new.context": "清除上下文 {{Command}}",
|
||||
"input.new_topic": "新話題 {{Command}}",
|
||||
@@ -126,6 +128,12 @@
|
||||
"message.quote": "引用",
|
||||
"message.regenerate.model": "切換模型",
|
||||
"message.useful": "有用",
|
||||
"navigation": {
|
||||
"first": "已經是第一條訊息",
|
||||
"last": "已經是最後一條訊息",
|
||||
"next": "下一條訊息",
|
||||
"prev": "上一條訊息"
|
||||
},
|
||||
"resend": "重新傳送",
|
||||
"save": "儲存",
|
||||
"settings.code_collapsible": "程式碼區塊可折疊",
|
||||
@@ -157,37 +165,42 @@
|
||||
"topics.edit.placeholder": "輸入新名稱",
|
||||
"topics.edit.title": "編輯名稱",
|
||||
"topics.export.image": "匯出為圖片",
|
||||
"topics.export.joplin": "匯出到 Joplin",
|
||||
"topics.export.md": "匯出為 Markdown",
|
||||
"topics.export.notion": "匯出到 Notion",
|
||||
"topics.export.obsidian": "匯出到 Obsidian",
|
||||
"topics.export.obsidian_atributes": "配置筆記屬性",
|
||||
"topics.export.obsidian_btn": "確定",
|
||||
"topics.export.obsidian_created": "建立時間",
|
||||
"topics.export.obsidian_created_placeholder": "請選擇建立時間",
|
||||
"topics.export.obsidian_export_failed": "匯出失敗",
|
||||
"topics.export.obsidian_export_success": "匯出成功",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
||||
"topics.export.obsidian_operate": "處理方式",
|
||||
"topics.export.obsidian_operate_append": "追加",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆蓋)",
|
||||
"topics.export.obsidian_operate_placeholder": "請選擇處理方式",
|
||||
"topics.export.obsidian_operate_prepend": "前置",
|
||||
"topics.export.obsidian_source": "來源",
|
||||
"topics.export.obsidian_source_placeholder": "請輸入來源",
|
||||
"topics.export.obsidian_tags": "標籤",
|
||||
"topics.export.obsidian_tags_placeholder": "請輸入標籤名稱,多個標籤用英文逗號分隔",
|
||||
"topics.export.obsidian_title": "標題",
|
||||
"topics.export.obsidian_title_placeholder": "請輸入標題",
|
||||
"topics.export.obsidian_title_required": "標題不能為空",
|
||||
"topics.export.title": "匯出",
|
||||
"topics.export.word": "匯出為 Word",
|
||||
"topics.export.yuque": "匯出到語雀",
|
||||
"topics.export.obsidian": "匯出到 Obsidian",
|
||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
||||
"topics.export.obsidian_fetch_failed": "獲取 Obsidian 文件夾結構失敗",
|
||||
"topics.export.obsidian_select_folder": "選擇 Obsidian 文件夾",
|
||||
"topics.export.obsidian_select_folder.btn": "確定",
|
||||
"topics.export.obsidian_export_success": "匯出成功",
|
||||
"topics.export.obsidian_export_failed": "匯出失敗",
|
||||
"topics.export.obsidian_show_md_files": "顯示md文件",
|
||||
"topics.export.obsidian_selected_path": "已選擇路徑",
|
||||
"topics.export.joplin": "匯出到 Joplin",
|
||||
"topics.list": "話題列表",
|
||||
"topics.move_to": "移動到",
|
||||
"topics.new": "開始新對話",
|
||||
"topics.pinned": "固定話題",
|
||||
"topics.prompt": "話題提示詞",
|
||||
"topics.prompt.edit.title": "編輯話題提示詞",
|
||||
"topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞",
|
||||
"topics.title": "話題",
|
||||
"topics.unpinned": "取消固定",
|
||||
"topics.new": "開始新對話",
|
||||
"translate": "翻譯",
|
||||
"navigation": {
|
||||
"prev": "上一條訊息",
|
||||
"next": "下一條訊息",
|
||||
"first": "已經是第一條訊息",
|
||||
"last": "已經是最後一條訊息"
|
||||
}
|
||||
"translate": "翻譯"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "折疊",
|
||||
@@ -197,6 +210,7 @@
|
||||
},
|
||||
"common": {
|
||||
"add": "新增",
|
||||
"advanced_settings": "進階設定",
|
||||
"and": "與",
|
||||
"assistant": "智慧代理人",
|
||||
"avatar": "頭像",
|
||||
@@ -205,6 +219,8 @@
|
||||
"chat": "聊天",
|
||||
"clear": "清除",
|
||||
"close": "關閉",
|
||||
"confirm": "確認",
|
||||
"copied": "已複製",
|
||||
"copy": "複製",
|
||||
"cut": "剪下",
|
||||
"default": "預設",
|
||||
@@ -214,6 +230,7 @@
|
||||
"download": "下載",
|
||||
"duplicate": "複製",
|
||||
"edit": "編輯",
|
||||
"expand": "展開",
|
||||
"footnote": "引用內容",
|
||||
"footnotes": "引用",
|
||||
"fullscreen": "已進入全螢幕模式,按 F11 結束",
|
||||
@@ -221,6 +238,7 @@
|
||||
"language": "語言",
|
||||
"model": "模型",
|
||||
"models": "模型",
|
||||
"more": "更多",
|
||||
"name": "名稱",
|
||||
"paste": "貼上",
|
||||
"prompt": "提示詞",
|
||||
@@ -233,12 +251,7 @@
|
||||
"select": "選擇",
|
||||
"topics": "話題",
|
||||
"warning": "警告",
|
||||
"you": "您",
|
||||
"copied": "已複製",
|
||||
"confirm": "確認",
|
||||
"more": "更多",
|
||||
"advanced_settings": "進階設定",
|
||||
"expand": "展開"
|
||||
"you": "您"
|
||||
},
|
||||
"docs": {
|
||||
"title": "說明文件"
|
||||
@@ -296,6 +309,12 @@
|
||||
"title": "檔案",
|
||||
"type": "類型"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
"keep_alive_time.title": "保持活躍時間",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "繼續聊天",
|
||||
"locate.message": "定位到訊息",
|
||||
@@ -365,13 +384,13 @@
|
||||
"threshold_too_large_or_small": "閾值不能大於 1 或小於 0",
|
||||
"threshold_tooltip": "用於衡量使用者問題與知識庫內容之間的相關性(0-1)",
|
||||
"title": "知識庫",
|
||||
"topN": "返回結果數量",
|
||||
"topN__too_large_or_small": "返回結果數量不能大於100或小於1",
|
||||
"topN_placeholder": "未設定",
|
||||
"topN_tooltip": "返回的匹配結果數量,數值越大,匹配結果越多,但消耗的 Token 也越多",
|
||||
"url_added": "網址已新增",
|
||||
"url_placeholder": "請輸入網址,多個網址用換行符號分隔",
|
||||
"urls": "網址",
|
||||
"topN": "返回結果數量",
|
||||
"topN_placeholder": "未設定",
|
||||
"topN__too_large_or_small": "返回結果數量不能大於100或小於1",
|
||||
"topN_tooltip": "返回的匹配結果數量,數值越大,匹配結果越多,但消耗的 Token 也越多"
|
||||
"urls": "網址"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "阿拉伯文",
|
||||
@@ -409,22 +428,22 @@
|
||||
"title": "Mermaid 圖表"
|
||||
},
|
||||
"message": {
|
||||
"attachments": {
|
||||
"pasted_text": "剪切板文件",
|
||||
"pasted_image": "剪切板圖片"
|
||||
},
|
||||
"api.check.model.title": "請選擇要偵測的模型",
|
||||
"api.connection.failed": "連接失敗",
|
||||
"api.connection.success": "連接成功",
|
||||
"assistant.added.content": "智慧代理人新增成功",
|
||||
"attachments": {
|
||||
"pasted_image": "剪切板圖片",
|
||||
"pasted_text": "剪切板文件"
|
||||
},
|
||||
"backup.failed": "備份失敗",
|
||||
"backup.start.success": "開始備份",
|
||||
"backup.success": "備份成功",
|
||||
"chat.completion.paused": "聊天完成已暫停",
|
||||
"citations": "參考文獻",
|
||||
"copied": "已複製!",
|
||||
"copy.success": "已複製!",
|
||||
"copy.failed": "複製失敗",
|
||||
"copy.success": "已複製!",
|
||||
"error.chunk_overlap_too_large": "分段重疊不能大於分段大小",
|
||||
"error.dimension_too_large": "內容尺寸過大",
|
||||
"error.enter.api.host": "請先輸入您的 API 主機地址",
|
||||
@@ -437,20 +456,20 @@
|
||||
"error.invalid.enter.model": "請選擇一個模型",
|
||||
"error.invalid.proxy.url": "無效的代理伺服器 URL",
|
||||
"error.invalid.webdav": "無效的 WebDAV 設定",
|
||||
"error.joplin.export": "匯出 Joplin 失敗,請保持 Joplin 已運行並檢查連接狀態或檢查設定",
|
||||
"error.joplin.no_config": "未設定 Joplin 授權Token 或 URL",
|
||||
"error.markdown.export.preconf": "導出 Markdown 文件到預先設定的路徑失敗",
|
||||
"error.markdown.export.specified": "導出 Markdown 文件失敗",
|
||||
"error.notion.export": "匯出 Notion 錯誤,請檢查連接狀態並對照文件檢查設定",
|
||||
"error.notion.no_api_key": "未設定 Notion API Key 或 Notion Database ID",
|
||||
"error.yuque.export": "匯出語雀錯誤,請檢查連接狀態並對照文件檢查設定",
|
||||
"error.yuque.no_config": "未設定語雀 Token 或知識庫 Url",
|
||||
"error.joplin.no_config": "未設定 Joplin 授權Token 或 URL",
|
||||
"error.joplin.export": "匯出 Joplin 失敗,請保持 Joplin 已運行並檢查連接狀態或檢查設定",
|
||||
"group.delete.content": "刪除分組訊息會刪除使用者提問和所有助手的回答",
|
||||
"group.delete.title": "刪除分組訊息",
|
||||
"ignore.knowledge.base": "網路模式開啟,忽略知識庫",
|
||||
"info.notion.block_reach_limit": "對話過長,自動分頁匯出到 Notion",
|
||||
"loading.notion.preparing": "正在準備匯出到 Notion...",
|
||||
"loading.notion.exporting_progress": "正在匯出到 Notion ({{current}}/{{total}})...",
|
||||
"loading.notion.preparing": "正在準備匯出到 Notion...",
|
||||
"mention.title": "切換模型回答",
|
||||
"message.code_style": "程式碼風格",
|
||||
"message.delete.content": "確定要刪除此訊息嗎?",
|
||||
@@ -473,22 +492,22 @@
|
||||
"restore.success": "恢復成功",
|
||||
"save.success.title": "儲存成功",
|
||||
"searching": "正在網路上搜尋...",
|
||||
"success.joplin.export": "成功匯出到 Joplin",
|
||||
"success.markdown.export.preconf": "成功導出 Markdown 文件到預先設定的路徑",
|
||||
"success.markdown.export.specified": "成功導出 Markdown 文件",
|
||||
"success.notion.export": "成功匯出到 Notion",
|
||||
"success.yuque.export": "成功匯出到語雀",
|
||||
"success.joplin.export": "成功匯出到 Joplin",
|
||||
"switch.disabled": "請等待當前回覆完成",
|
||||
"tools": {
|
||||
"completed": "已完成",
|
||||
"invoking": "調用中"
|
||||
},
|
||||
"topic.added": "新話題已新增",
|
||||
"upgrade.success.button": "重新啟動",
|
||||
"upgrade.success.content": "請重新啟動程式以完成升級",
|
||||
"upgrade.success.title": "升級成功",
|
||||
"warn.notion.exporting": "正在匯出到 Notion,請勿重複請求匯出!",
|
||||
"warning.rate.limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試",
|
||||
"tools": {
|
||||
"invoking": "調用中",
|
||||
"completed": "已完成"
|
||||
}
|
||||
"warning.rate.limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "新增到側邊欄",
|
||||
@@ -527,7 +546,7 @@
|
||||
"embedding": "嵌入",
|
||||
"embedding_model": "嵌入模型",
|
||||
"embedding_model_tooltip": "在設定->模型服務中點選管理按鈕新增",
|
||||
"free": "免費",
|
||||
"function_calling": "函數調用",
|
||||
"no_matches": "無可用模型",
|
||||
"parameter_name": "參數名稱",
|
||||
"parameter_type": {
|
||||
@@ -537,22 +556,22 @@
|
||||
"string": "文字"
|
||||
},
|
||||
"pinned": "已固定",
|
||||
"reasoning": "推理",
|
||||
"rerank_model": "重排模型",
|
||||
"rerank_model_support_provider": "目前重排序模型僅支持部分服務商 ({{provider}})",
|
||||
"rerank_model_tooltip": "在設定->模型服務中點擊管理按鈕添加",
|
||||
"search": "搜尋模型...",
|
||||
"stream_output": "串流輸出",
|
||||
"function_calling": "函數調用",
|
||||
"type": {
|
||||
"embedding": "嵌入",
|
||||
"free": "免費",
|
||||
"function_calling": "工具",
|
||||
"reasoning": "推理",
|
||||
"rerank": "重排",
|
||||
"select": "選擇模型類型",
|
||||
"text": "文字",
|
||||
"vision": "影像",
|
||||
"function_calling": "函數調用"
|
||||
},
|
||||
"vision": "視覺",
|
||||
"websearch": "網路搜尋",
|
||||
"rerank_model": "重排序模型",
|
||||
"rerank_model_tooltip": "在設定->模型服務中點擊管理按鈕添加"
|
||||
"vision": "視覺",
|
||||
"websearch": "網路搜尋"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "伸縮對話框",
|
||||
@@ -605,10 +624,12 @@
|
||||
},
|
||||
"provider": {
|
||||
"aihubmix": "AiHubMix",
|
||||
"alayanew": "Alaya NeW",
|
||||
"anthropic": "Anthropic",
|
||||
"azure-openai": "Azure OpenAI",
|
||||
"baichuan": "百川",
|
||||
"baidu-cloud": "百度雲千帆",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "阿里雲百鍊",
|
||||
"deepseek": "深度求索",
|
||||
"dmxapi": "DMXAPI",
|
||||
@@ -617,6 +638,7 @@
|
||||
"gemini": "Gemini",
|
||||
"gitee-ai": "Gitee AI",
|
||||
"github": "GitHub Models",
|
||||
"gpustack": "GPUStack",
|
||||
"graphrag-kylin-mountain": "GraphRAG",
|
||||
"grok": "Grok",
|
||||
"groq": "Groq",
|
||||
@@ -645,10 +667,7 @@
|
||||
"xirang": "天翼雲息壤",
|
||||
"yi": "零一萬物",
|
||||
"zhinao": "360 智腦",
|
||||
"zhipu": "智譜 AI",
|
||||
"copilot": "GitHub Copilot",
|
||||
"gpustack": "GPUStack",
|
||||
"alayanew": "Alaya NeW"
|
||||
"zhipu": "智譜 AI"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "確定要復原資料嗎?",
|
||||
@@ -664,12 +683,6 @@
|
||||
},
|
||||
"title": "資料復原"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
"keep_alive_time.title": "保持活躍時間",
|
||||
"title": "GPUStack"
|
||||
},
|
||||
"settings": {
|
||||
"about": "關於與回饋",
|
||||
"about.checkingUpdate": "正在檢查更新...",
|
||||
@@ -716,15 +729,30 @@
|
||||
"data.title": "資料目錄",
|
||||
"hour_interval_one": "{{count}} 小時",
|
||||
"hour_interval_other": "{{count}} 小時",
|
||||
"minute_interval_one": "{{count}} 分鐘",
|
||||
"minute_interval_other": "{{count}} 分鐘",
|
||||
"markdown_export.title": "Markdown 匯出",
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "檢查",
|
||||
"empty_token": "請先輸入 Joplin 授權Token",
|
||||
"empty_url": "請先輸入 Joplin 剪輯服務 URL",
|
||||
"fail": "Joplin 連接驗證失敗",
|
||||
"success": "Joplin 連接驗證成功"
|
||||
},
|
||||
"help": "在 Joplin 選項中,啟用剪輯服務(無需安裝瀏覽器外掛),確認埠編號,並複製授權Token",
|
||||
"title": "Joplin 設定",
|
||||
"token": "Joplin 授權Token",
|
||||
"token_placeholder": "請輸入 Joplin 授權Token",
|
||||
"url": "Joplin 剪輯服務 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "開啟後,匯出Markdown時會強制使用$$來標記LaTeX公式。注意:該項也會影響所有透過Markdown匯出的方式,如Notion、語雀等。",
|
||||
"markdown_export.force_dollar_math.title": "LaTeX公式強制使用$$",
|
||||
"markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框。",
|
||||
"markdown_export.path": "預設匯出路徑",
|
||||
"markdown_export.path_placeholder": "匯出路徑",
|
||||
"markdown_export.select": "選擇",
|
||||
"markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框。",
|
||||
"markdown_export.force_dollar_math.title": "LaTeX公式強制使用$$",
|
||||
"markdown_export.force_dollar_math.help": "開啟後,匯出Markdown時會強制使用$$來標記LaTeX公式。注意:該項也會影響所有透過Markdown匯出的方式,如Notion、語雀等。",
|
||||
"markdown_export.title": "Markdown 匯出",
|
||||
"minute_interval_one": "{{count}} 分鐘",
|
||||
"minute_interval_other": "{{count}} 分鐘",
|
||||
"notion.api_key": "Notion 金鑰",
|
||||
"notion.api_key_placeholder": "請輸入 Notion 金鑰",
|
||||
"notion.auto_split": "匯出對話時自動分頁",
|
||||
@@ -746,11 +774,22 @@
|
||||
"notion.split_size_help": "Notion 免費版使用者建議設定為 90,進階版使用者建議設定為 24990,預設值為 90",
|
||||
"notion.split_size_placeholder": "請輸入每頁塊數限制 (預設 90)",
|
||||
"notion.title": "Notion 設定",
|
||||
"obsidian": {
|
||||
"folder": "資料夾",
|
||||
"folder_placeholder": "請輸入資料夾名稱",
|
||||
"tags": "全域標籤",
|
||||
"tags_placeholder": "請輸入標籤名稱,多個標籤用英文逗號分隔。Obsidian 不可用純數字。",
|
||||
"title": "Obsidian 設定",
|
||||
"vault": "保險庫",
|
||||
"vault_placeholder": "請輸入保險庫名稱"
|
||||
},
|
||||
"title": "資料設定",
|
||||
"webdav": {
|
||||
"autoSync": "自動備份",
|
||||
"autoSync.off": "關閉",
|
||||
"backup.button": "備份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "請輸入備份文件名",
|
||||
"backup.modal.title": "備份到 WebDAV",
|
||||
"host": "WebDAV 主機位址",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 小時",
|
||||
@@ -763,18 +802,16 @@
|
||||
"path": "WebDAV 路徑",
|
||||
"path.placeholder": "/backup",
|
||||
"restore.button": "從 WebDAV 恢復",
|
||||
"restore.confirm.content": "從 WebDAV 恢復將覆蓋目前資料,是否繼續?",
|
||||
"restore.confirm.title": "復元確認",
|
||||
"restore.content": "從 WebDAV 恢復將覆蓋目前資料,是否繼續?",
|
||||
"restore.modal.select.placeholder": "請選擇要恢復的備份文件",
|
||||
"restore.modal.title": "從 WebDAV 恢復",
|
||||
"restore.title": "從 WebDAV 恢復",
|
||||
"syncError": "備份錯誤",
|
||||
"syncStatus": "備份狀態",
|
||||
"title": "WebDAV",
|
||||
"user": "WebDAV 使用者名稱",
|
||||
"backup.modal.title": "備份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "請輸入備份文件名",
|
||||
"restore.modal.title": "從 WebDAV 恢復",
|
||||
"restore.modal.select.placeholder": "請選擇要恢復的備份文件",
|
||||
"restore.confirm.title": "復元確認",
|
||||
"restore.confirm.content": "從 WebDAV 恢復將覆蓋目前資料,是否繼續?"
|
||||
"user": "WebDAV 使用者名稱"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -790,36 +827,6 @@
|
||||
"title": "語雀設定",
|
||||
"token": "語雀 Token",
|
||||
"token_placeholder": "請輸入語雀 Token"
|
||||
},
|
||||
"obsidian": {
|
||||
"check": {
|
||||
"button": "檢查",
|
||||
"empty_url": "請先輸入 Obsidian REST API URL",
|
||||
"empty_api_key": "請先輸入 Obsidian API Key",
|
||||
"fail": "Obsidian 連接驗證失敗",
|
||||
"success": "Obsidian 連接驗證成功"
|
||||
},
|
||||
"help": "先安裝 Obsidian 插件 Local REST API,然後獲取 Obsidian API Key",
|
||||
"url": "Obsidian 知識庫 URL",
|
||||
"url_placeholder": "http://127.0.0.1:27123/",
|
||||
"title": "Obsidian 設定",
|
||||
"api_key": "Obsidian API Key",
|
||||
"api_key_placeholder": "請輸入 Obsidian API Key"
|
||||
},
|
||||
"joplin": {
|
||||
"check": {
|
||||
"button": "檢查",
|
||||
"empty_url": "請先輸入 Joplin 剪輯服務 URL",
|
||||
"empty_token": "請先輸入 Joplin 授權Token",
|
||||
"fail": "Joplin 連接驗證失敗",
|
||||
"success": "Joplin 連接驗證成功"
|
||||
},
|
||||
"title": "Joplin 設定",
|
||||
"help": "在 Joplin 選項中,啟用剪輯服務(無需安裝瀏覽器外掛),確認埠編號,並複製授權Token",
|
||||
"url": "Joplin 剪輯服務 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/",
|
||||
"token": "Joplin 授權Token",
|
||||
"token_placeholder": "請輸入 Joplin 授權Token"
|
||||
}
|
||||
},
|
||||
"display.assistant.title": "助手設定",
|
||||
@@ -865,12 +872,15 @@
|
||||
"input.target_language.english": "英文",
|
||||
"input.target_language.japanese": "日文",
|
||||
"input.target_language.russian": "俄文",
|
||||
"launch.onboot": "開機自動啟動",
|
||||
"launch.title": "啟動",
|
||||
"launch.totray": "啟動時最小化到系统匣",
|
||||
"mcp": {
|
||||
"actions": "操作",
|
||||
"active": "啟用",
|
||||
"addError": "添加伺服器失敗",
|
||||
"addServer": "新增伺服器",
|
||||
"addSuccess": "伺服器新增成功",
|
||||
"addError": "添加伺服器失敗",
|
||||
"args": "參數",
|
||||
"argsTooltip": "每個參數佔一行",
|
||||
"baseUrlTooltip": "遠端 URL 地址",
|
||||
@@ -881,55 +891,51 @@
|
||||
"confirmDeleteMessage": "您確定要刪除該伺服器嗎?",
|
||||
"deleteError": "刪除伺服器失敗",
|
||||
"deleteSuccess": "伺服器刪除成功",
|
||||
"dependenciesInstall": "安裝相依套件",
|
||||
"dependenciesInstalling": "正在安裝相依套件...",
|
||||
"description": "描述",
|
||||
"duplicateName": "已存在相同名稱的伺服器",
|
||||
"editJson": "編輯JSON",
|
||||
"editServer": "編輯伺服器",
|
||||
"env": "環境變數",
|
||||
"envTooltip": "格式:KEY=value,每行一個",
|
||||
"findMore": "更多 MCP 伺服器",
|
||||
"install": "安裝",
|
||||
"installError": "安裝相依套件失敗",
|
||||
"installSuccess": "相依套件安裝成功",
|
||||
"jsonFormatError": "JSON格式錯誤",
|
||||
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確。",
|
||||
"jsonSaveError": "保存JSON配置失敗",
|
||||
"jsonSaveSuccess": "JSON配置已儲存",
|
||||
"missingDependencies": "缺失,請安裝它以繼續",
|
||||
"name": "名稱",
|
||||
"nameRequired": "請輸入伺服器名稱",
|
||||
"noServers": "未設定伺服器",
|
||||
"npx_list": {
|
||||
"actions": "操作",
|
||||
"desc": "搜索並添加 npm 包作為 MCP 服務",
|
||||
"description": "描述",
|
||||
"no_packages": "未找到包",
|
||||
"npm": "NPM",
|
||||
"package_name": "包名稱",
|
||||
"scope_placeholder": "輸入 npm 作用域 (例如 @your-org)",
|
||||
"scope_required": "請輸入 npm 作用域",
|
||||
"search": "搜索",
|
||||
"search_error": "搜索失敗",
|
||||
"title": "NPX 包列表",
|
||||
"usage": "用法",
|
||||
"version": "版本"
|
||||
},
|
||||
"serverPlural": "伺服器",
|
||||
"serverSingular": "伺服器",
|
||||
"title": "MCP 伺服器",
|
||||
"type": "類型",
|
||||
"updateSuccess": "伺服器更新成功",
|
||||
"updateError": "更新伺服器失敗",
|
||||
"url": "URL",
|
||||
"toggleError": "切換失敗",
|
||||
"dependenciesInstalling": "正在安裝相依套件...",
|
||||
"dependenciesInstall": "安裝相依套件",
|
||||
"installSuccess": "相依套件安裝成功",
|
||||
"installError": "安裝相依套件失敗",
|
||||
"missingDependencies": "缺失,請安裝它以繼續",
|
||||
"install": "安裝",
|
||||
"npx_list": {
|
||||
"title": "NPX 包列表",
|
||||
"desc": "搜索並添加 npm 包作為 MCP 服務",
|
||||
"scope_placeholder": "輸入 npm 作用域 (例如 @your-org)",
|
||||
"search": "搜索",
|
||||
"package_name": "包名稱",
|
||||
"description": "描述",
|
||||
"usage": "用法",
|
||||
"npm": "NPM",
|
||||
"version": "版本",
|
||||
"actions": "操作",
|
||||
"scope_required": "請輸入 npm 作用域",
|
||||
"no_packages": "未找到包",
|
||||
"search_error": "搜索失敗"
|
||||
},
|
||||
"editJson": "編輯JSON",
|
||||
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確。",
|
||||
"jsonFormatError": "JSON格式錯誤",
|
||||
"jsonSaveSuccess": "JSON配置已儲存",
|
||||
"jsonSaveError": "保存JSON配置失敗"
|
||||
"type": "類型",
|
||||
"updateError": "更新伺服器失敗",
|
||||
"updateSuccess": "伺服器更新成功",
|
||||
"url": "URL"
|
||||
},
|
||||
"messages.divider": "訊息間顯示分隔線",
|
||||
"messages.navigation": "訊息導航",
|
||||
"messages.navigation.none": "不顯示",
|
||||
"messages.navigation.buttons": "上下按鈕",
|
||||
"messages.navigation.anchor": "對話錨點",
|
||||
"messages.grid_columns": "訊息網格展示列數",
|
||||
"messages.grid_popover_trigger": "網格詳細資訊觸發",
|
||||
"messages.grid_popover_trigger.click": "點選顯示",
|
||||
@@ -943,6 +949,10 @@
|
||||
"messages.math_engine": "Markdown 渲染輸入訊息",
|
||||
"messages.metrics": "首字延遲 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||
"messages.model.title": "模型設定",
|
||||
"messages.navigation": "訊息導航",
|
||||
"messages.navigation.anchor": "對話錨點",
|
||||
"messages.navigation.buttons": "上下按鈕",
|
||||
"messages.navigation.none": "不顯示",
|
||||
"messages.title": "訊息設定",
|
||||
"messages.use_serif_font": "使用襯線字型",
|
||||
"model": "預設模型",
|
||||
@@ -1005,43 +1015,43 @@
|
||||
"check": "檢查",
|
||||
"check_all_keys": "檢查所有金鑰",
|
||||
"check_multiple_keys": "檢查多個 API 金鑰",
|
||||
"copilot": {
|
||||
"auth_failed": "Github Copilot認證失敗",
|
||||
"auth_success": "Github Copilot 認證成功",
|
||||
"auth_success_title": "認證成功",
|
||||
"code_failed": "獲取 Device Code失敗,請重試",
|
||||
"code_generated_desc": "請將設備代碼複製到下面的瀏覽器連結中",
|
||||
"code_generated_title": "獲取設備代碼",
|
||||
"confirm_login": "過度使用可能會導致您的 Github 帳號遭到封號,請謹慎使用!!!!",
|
||||
"confirm_title": "風險警告",
|
||||
"connect": "連接 Github",
|
||||
"custom_headers": "自訂請求標頭",
|
||||
"description": "您的 Github 帳號需要訂閱 Copilot",
|
||||
"expand": "展開",
|
||||
"headers_description": "自訂請求標頭(json格式)",
|
||||
"invalid_json": "JSON 格式錯誤",
|
||||
"login": "登入 Github",
|
||||
"logout": "退出 Github",
|
||||
"logout_failed": "退出失敗,請重試",
|
||||
"logout_success": "已成功登出",
|
||||
"model_setting": "模型設定",
|
||||
"open_verification_first": "請先點擊上方連結訪問驗證頁面",
|
||||
"rate_limit": "速率限制",
|
||||
"tooltip": "使用 Github Copilot 需要先登入 Github"
|
||||
},
|
||||
"delete.content": "確定要刪除此提供者嗎?",
|
||||
"delete.title": "刪除提供者",
|
||||
"docs_check": "檢查",
|
||||
"docs_more_details": "檢視更多細節",
|
||||
"get_api_key": "點選這裡取得金鑰",
|
||||
"is_not_support_array_content": "開啟相容模式",
|
||||
"no_models": "請先新增模型再檢查 API 連接",
|
||||
"not_checked": "未檢查",
|
||||
"remove_duplicate_keys": "移除重複金鑰",
|
||||
"remove_invalid_keys": "刪除無效金鑰",
|
||||
"search": "搜尋模型平臺...",
|
||||
"search_placeholder": "搜尋模型 ID 或名稱",
|
||||
"title": "模型提供者",
|
||||
"is_not_support_array_content": "開啟相容模式",
|
||||
"copilot": {
|
||||
"tooltip": "使用 Github Copilot 需要先登入 Github",
|
||||
"description": "您的 Github 帳號需要訂閱 Copilot",
|
||||
"login": "登入 Github",
|
||||
"connect": "連接 Github",
|
||||
"logout": "退出 Github",
|
||||
"auth_success_title": "認證成功",
|
||||
"code_generated_title": "獲取設備代碼",
|
||||
"code_generated_desc": "請將設備代碼複製到下面的瀏覽器連結中",
|
||||
"code_failed": "獲取 Device Code失敗,請重試",
|
||||
"auth_success": "Github Copilot 認證成功",
|
||||
"auth_failed": "Github Copilot認證失敗",
|
||||
"logout_success": "已成功登出",
|
||||
"logout_failed": "退出失敗,請重試",
|
||||
"confirm_title": "風險警告",
|
||||
"confirm_login": "過度使用可能會導致您的 Github 帳號遭到封號,請謹慎使用!!!!",
|
||||
"rate_limit": "速率限制",
|
||||
"custom_headers": "自訂請求標頭",
|
||||
"headers_description": "自訂請求標頭(json格式)",
|
||||
"expand": "展開",
|
||||
"model_setting": "模型設定",
|
||||
"invalid_json": "JSON 格式錯誤",
|
||||
"open_verification_first": "請先點擊上方連結訪問驗證頁面"
|
||||
}
|
||||
"title": "模型提供者"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1056,9 +1066,9 @@
|
||||
"quickAssistant": {
|
||||
"click_tray_to_show": "點選工具列圖示啟動",
|
||||
"enable_quick_assistant": "啟用快捷助手",
|
||||
"read_clipboard_at_startup": "啟動時讀取剪貼簿",
|
||||
"title": "快捷助手",
|
||||
"use_shortcut_to_show": "右鍵點選工具列圖示或使用快捷鍵啟動",
|
||||
"read_clipboard_at_startup": "啟動時讀取剪貼簿"
|
||||
"use_shortcut_to_show": "右鍵點選工具列圖示或使用快捷鍵啟動"
|
||||
},
|
||||
"shortcuts": {
|
||||
"action": "操作",
|
||||
@@ -1095,14 +1105,18 @@
|
||||
"topic.position.left": "左側",
|
||||
"topic.position.right": "右側",
|
||||
"topic.show.time": "顯示話題時間",
|
||||
"tray.title": "啟用系統工具列圖示",
|
||||
"tray.onclose": "關閉時最小化到系统匣",
|
||||
"tray.show": "顯示系统匣圖示",
|
||||
"tray.title": "系统匣",
|
||||
"websearch": {
|
||||
"blacklist": "黑名單",
|
||||
"blacklist_description": "以下網站不會出現在搜尋結果中",
|
||||
"blacklist_tooltip": "請使用以下格式 (換行符號分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
"check": "檢查",
|
||||
"check_success": "驗證成功",
|
||||
"check_failed": "驗證失敗",
|
||||
"check_success": "驗證成功",
|
||||
"enhance_mode": "搜索增強模式",
|
||||
"enhance_mode_tooltip": "使用預設模型提取關鍵詞後搜索",
|
||||
"get_api_key": "點選這裡取得金鑰",
|
||||
"no_provider_selected": "請選擇搜尋服務商後再檢查",
|
||||
"search_max_result": "搜尋結果個數",
|
||||
@@ -1110,8 +1124,6 @@
|
||||
"search_provider_placeholder": "選擇一個搜尋服務商",
|
||||
"search_result_default": "預設",
|
||||
"search_with_time": "搜尋包含日期",
|
||||
"enhance_mode": "搜索增強模式",
|
||||
"enhance_mode_tooltip": "使用預設模型提取關鍵詞後搜索",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API 金鑰",
|
||||
"api_key.placeholder": "請輸入 Tavily API 金鑰",
|
||||
@@ -1158,4 +1170,4 @@
|
||||
"visualization": "視覺化"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
本目录文件使用机器翻译,请勿编辑
|
||||
This directory file is machine translated, please do not edit
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,250 +1,390 @@
|
||||
export type GroupTranslations = {
|
||||
[key: string]: {
|
||||
'el-GR': string
|
||||
'en-US': string
|
||||
'es-ES': string
|
||||
'fr-FR': string
|
||||
'zh-CN': string
|
||||
'zh-TW': string
|
||||
'ru-RU': string
|
||||
'ja-JP': string
|
||||
'pt-PT': string
|
||||
}
|
||||
}
|
||||
|
||||
export const groupTranslations: GroupTranslations = {
|
||||
我的: {
|
||||
'el-GR': 'Τα δικά μου',
|
||||
'en-US': 'My Agents',
|
||||
'es-ES': 'Mis agentes',
|
||||
'fr-FR': 'Mes agents',
|
||||
'zh-CN': '我的',
|
||||
'zh-TW': '我的',
|
||||
'ru-RU': 'Мои агенты',
|
||||
'ja-JP': '私のエージェント'
|
||||
'ja-JP': '私のエージェント',
|
||||
'pt-PT': 'Meus Agentes'
|
||||
},
|
||||
职业: {
|
||||
'el-GR': 'Επαγγελμα',
|
||||
'en-US': 'Career',
|
||||
'es-ES': 'Profesional',
|
||||
'fr-FR': 'Professionnel',
|
||||
'zh-CN': '职业',
|
||||
'zh-TW': '職業',
|
||||
'ru-RU': 'Карьера',
|
||||
'ja-JP': 'キャリア'
|
||||
'ja-JP': 'キャリア',
|
||||
'pt-PT': 'Profissional'
|
||||
},
|
||||
商业: {
|
||||
'el-GR': 'Εμπορικός',
|
||||
'en-US': 'Business',
|
||||
'es-ES': 'Negocio',
|
||||
'fr-FR': 'Commercial',
|
||||
'zh-CN': '商业',
|
||||
'zh-TW': '商業',
|
||||
'ru-RU': 'Бизнес',
|
||||
'ja-JP': 'ビジネス'
|
||||
'ja-JP': 'ビジネス',
|
||||
'pt-PT': 'Negócio'
|
||||
},
|
||||
工具: {
|
||||
'el-GR': 'Εργαλεία',
|
||||
'en-US': 'Tools',
|
||||
'es-ES': 'Herramientas',
|
||||
'fr-FR': 'Outils',
|
||||
'zh-CN': '工具',
|
||||
'zh-TW': '工具',
|
||||
'ru-RU': 'Инструменты',
|
||||
'ja-JP': 'ツール'
|
||||
'ja-JP': 'ツール',
|
||||
'pt-PT': 'Ferramentas'
|
||||
},
|
||||
语言: {
|
||||
'el-GR': 'Γλώσσα',
|
||||
'en-US': 'Language',
|
||||
'es-ES': 'Idioma',
|
||||
'fr-FR': 'Langue',
|
||||
'zh-CN': '语言',
|
||||
'zh-TW': '語言',
|
||||
'ru-RU': 'Язык',
|
||||
'ja-JP': '言語'
|
||||
'ja-JP': '言語',
|
||||
'pt-PT': 'Idioma'
|
||||
},
|
||||
办公: {
|
||||
'el-GR': 'Γραφείο',
|
||||
'en-US': 'Office',
|
||||
'es-ES': 'Oficina',
|
||||
'fr-FR': 'Bureau',
|
||||
'zh-CN': '办公',
|
||||
'zh-TW': '辦公',
|
||||
'ru-RU': 'Офис',
|
||||
'ja-JP': 'オフィス'
|
||||
'ja-JP': 'オフィス',
|
||||
'pt-PT': 'Escritório'
|
||||
},
|
||||
通用: {
|
||||
'el-GR': 'Γενικά',
|
||||
'en-US': 'General',
|
||||
'es-ES': 'General',
|
||||
'fr-FR': 'Général',
|
||||
'zh-CN': '通用',
|
||||
'zh-TW': '通用',
|
||||
'ru-RU': 'Общее',
|
||||
'ja-JP': '一般'
|
||||
'ja-JP': '一般',
|
||||
'pt-PT': 'Geral'
|
||||
},
|
||||
写作: {
|
||||
'el-GR': 'Γράφημα',
|
||||
'en-US': 'Writing',
|
||||
'es-ES': 'Escritura',
|
||||
'fr-FR': 'Écriture',
|
||||
'zh-CN': '写作',
|
||||
'zh-TW': '寫作',
|
||||
'ru-RU': 'Письмо',
|
||||
'ja-JP': '書き込み'
|
||||
'ja-JP': '書き込み',
|
||||
'pt-PT': 'Escrita'
|
||||
},
|
||||
精选: {
|
||||
'el-GR': 'Επιλεγμένο',
|
||||
'en-US': 'Featured',
|
||||
'es-ES': 'Destacado',
|
||||
'fr-FR': 'Sélection',
|
||||
'zh-CN': '精选',
|
||||
'zh-TW': '精選',
|
||||
'ru-RU': 'Избранное',
|
||||
'ja-JP': '特集'
|
||||
'ja-JP': '特集',
|
||||
'pt-PT': 'Destaque'
|
||||
},
|
||||
编程: {
|
||||
'el-GR': 'Προγραμματισμός',
|
||||
'en-US': 'Programming',
|
||||
'es-ES': 'Programación',
|
||||
'fr-FR': 'Programmation',
|
||||
'zh-CN': '编程',
|
||||
'zh-TW': '編程',
|
||||
'ru-RU': 'Программирование',
|
||||
'ja-JP': 'プログラミング'
|
||||
'ja-JP': 'プログラミング',
|
||||
'pt-PT': 'Programação'
|
||||
},
|
||||
情感: {
|
||||
'el-GR': 'Αίσθημα',
|
||||
'en-US': 'Emotion',
|
||||
'es-ES': 'Emoción',
|
||||
'fr-FR': 'Émotion',
|
||||
'zh-CN': '情感',
|
||||
'zh-TW': '情感',
|
||||
'ru-RU': 'Эмоции',
|
||||
'ja-JP': '感情'
|
||||
'ja-JP': '感情',
|
||||
'pt-PT': 'Emoção'
|
||||
},
|
||||
教育: {
|
||||
'el-GR': 'Εκπαίδευση',
|
||||
'en-US': 'Education',
|
||||
'es-ES': 'Educación',
|
||||
'fr-FR': 'Éducation',
|
||||
'zh-CN': '教育',
|
||||
'zh-TW': '教育',
|
||||
'ru-RU': 'Образование',
|
||||
'ja-JP': '教育'
|
||||
'ja-JP': '教育',
|
||||
'pt-PT': 'Educação'
|
||||
},
|
||||
创意: {
|
||||
'el-GR': 'Κreativiteit',
|
||||
'en-US': 'Creative',
|
||||
'es-ES': 'Creativo',
|
||||
'fr-FR': 'Créatif',
|
||||
'zh-CN': '创意',
|
||||
'zh-TW': '創意',
|
||||
'ru-RU': 'Креатив',
|
||||
'ja-JP': 'クリエイティブ'
|
||||
'ja-JP': 'クリエイティブ',
|
||||
'pt-PT': 'Criativo'
|
||||
},
|
||||
学术: {
|
||||
'el-GR': 'Ακαδημικός',
|
||||
'en-US': 'Academic',
|
||||
'es-ES': 'Académico',
|
||||
'fr-FR': 'Académique',
|
||||
'zh-CN': '学术',
|
||||
'zh-TW': '學術',
|
||||
'ru-RU': 'Академический',
|
||||
'ja-JP': 'アカデミック'
|
||||
'ja-JP': 'アカデミック',
|
||||
'pt-PT': 'Académico'
|
||||
},
|
||||
设计: {
|
||||
'el-GR': 'Δημιουργικό',
|
||||
'en-US': 'Design',
|
||||
'es-ES': 'Diseño',
|
||||
'fr-FR': 'Design',
|
||||
'zh-CN': '设计',
|
||||
'zh-TW': '設計',
|
||||
'ru-RU': 'Дизайн',
|
||||
'ja-JP': 'デザイン'
|
||||
'ja-JP': 'デザイン',
|
||||
'pt-PT': 'Design'
|
||||
},
|
||||
艺术: {
|
||||
'el-GR': 'Τέχνη',
|
||||
'en-US': 'Art',
|
||||
'es-ES': 'Arte',
|
||||
'fr-FR': 'Art',
|
||||
'zh-CN': '艺术',
|
||||
'zh-TW': '藝術',
|
||||
'ru-RU': 'Искусство',
|
||||
'ja-JP': 'アート'
|
||||
'ja-JP': 'アート',
|
||||
'pt-PT': 'Arte'
|
||||
},
|
||||
娱乐: {
|
||||
'el-GR': 'Αναψυχή',
|
||||
'en-US': 'Entertainment',
|
||||
'es-ES': 'Entretenimiento',
|
||||
'fr-FR': 'Divertissement',
|
||||
'zh-CN': '娱乐',
|
||||
'zh-TW': '娛樂',
|
||||
'ru-RU': 'Развлечения',
|
||||
'ja-JP': 'エンターテイメント'
|
||||
'ja-JP': 'エンターテイメント',
|
||||
'pt-PT': 'Entretenimento'
|
||||
},
|
||||
生活: {
|
||||
'el-GR': 'Ζωή',
|
||||
'en-US': 'Life',
|
||||
'es-ES': 'Vida',
|
||||
'fr-FR': 'Vie',
|
||||
'zh-CN': '生活',
|
||||
'zh-TW': '生活',
|
||||
'ru-RU': 'Жизнь',
|
||||
'ja-JP': '生活'
|
||||
'ja-JP': '生活',
|
||||
'pt-PT': 'Vida'
|
||||
},
|
||||
医疗: {
|
||||
'el-GR': 'Υγεία',
|
||||
'en-US': 'Medical',
|
||||
'es-ES': 'Médico',
|
||||
'fr-FR': 'Médical',
|
||||
'zh-CN': '医疗',
|
||||
'zh-TW': '醫療',
|
||||
'ru-RU': 'Медицина',
|
||||
'ja-JP': '医療'
|
||||
'ja-JP': '医療',
|
||||
'pt-PT': 'Saúde'
|
||||
},
|
||||
游戏: {
|
||||
'el-GR': 'Παιχνίδια',
|
||||
'en-US': 'Games',
|
||||
'es-ES': 'Juegos',
|
||||
'fr-FR': 'Jeux',
|
||||
'zh-CN': '游戏',
|
||||
'zh-TW': '遊戲',
|
||||
'ru-RU': 'Игры',
|
||||
'ja-JP': 'ゲーム'
|
||||
'ja-JP': 'ゲーム',
|
||||
'pt-PT': 'Jogos'
|
||||
},
|
||||
翻译: {
|
||||
'el-GR': 'Γραφήματα',
|
||||
'en-US': 'Translation',
|
||||
'es-ES': 'Traducción',
|
||||
'fr-FR': 'Traduction',
|
||||
'zh-CN': '翻译',
|
||||
'zh-TW': '翻譯',
|
||||
'ru-RU': 'Перевод',
|
||||
'ja-JP': '翻訳'
|
||||
'ja-JP': '翻訳',
|
||||
'pt-PT': 'Tradução'
|
||||
},
|
||||
音乐: {
|
||||
'el-GR': 'Μουσική',
|
||||
'en-US': 'Music',
|
||||
'es-ES': 'Música',
|
||||
'fr-FR': 'Musique',
|
||||
'zh-CN': '音乐',
|
||||
'zh-TW': '音樂',
|
||||
'ru-RU': 'Музыка',
|
||||
'ja-JP': '音楽'
|
||||
'ja-JP': '音楽',
|
||||
'pt-PT': 'Música'
|
||||
},
|
||||
点评: {
|
||||
'el-GR': 'Αξιολόγηση',
|
||||
'en-US': 'Review',
|
||||
'es-ES': 'Revisión',
|
||||
'fr-FR': 'Avis',
|
||||
'zh-CN': '点评',
|
||||
'zh-TW': '點評',
|
||||
'ru-RU': 'Обзор',
|
||||
'ja-JP': 'レビュー'
|
||||
'ja-JP': 'レビュー',
|
||||
'pt-PT': 'Revisão'
|
||||
},
|
||||
文案: {
|
||||
'el-GR': 'Γραφήματα',
|
||||
'en-US': 'Copywriting',
|
||||
'es-ES': 'Redacción',
|
||||
'fr-FR': 'Rédaction',
|
||||
'zh-CN': '文案',
|
||||
'zh-TW': '文案',
|
||||
'ru-RU': 'Копирайтинг',
|
||||
'ja-JP': 'コピーライティング'
|
||||
'ja-JP': 'コピーライティング',
|
||||
'pt-PT': 'Escrita'
|
||||
},
|
||||
百科: {
|
||||
'el-GR': 'Εγκυκλοπαίδεια',
|
||||
'en-US': 'Encyclopedia',
|
||||
'es-ES': 'Enciclopedia',
|
||||
'fr-FR': 'Encyclopédie',
|
||||
'zh-CN': '百科',
|
||||
'zh-TW': '百科',
|
||||
'ru-RU': 'Энциклопедия',
|
||||
'ja-JP': '百科事典'
|
||||
'ja-JP': '百科事典',
|
||||
'pt-PT': 'Enciclopédia'
|
||||
},
|
||||
健康: {
|
||||
'el-GR': 'Υγεία',
|
||||
'en-US': 'Health',
|
||||
'es-ES': 'Salud',
|
||||
'fr-FR': 'Santé',
|
||||
'zh-CN': '健康',
|
||||
'zh-TW': '健康',
|
||||
'ru-RU': 'Здоровье',
|
||||
'ja-JP': '健康'
|
||||
'ja-JP': '健康',
|
||||
'pt-PT': 'Saúde'
|
||||
},
|
||||
营销: {
|
||||
'el-GR': 'Μάρκετινγκ',
|
||||
'en-US': 'Marketing',
|
||||
'es-ES': 'Marketing',
|
||||
'fr-FR': 'Marketing',
|
||||
'zh-CN': '营销',
|
||||
'zh-TW': '營銷',
|
||||
'ru-RU': 'Маркетинг',
|
||||
'ja-JP': 'マーケティング'
|
||||
'ja-JP': 'マーケティング',
|
||||
'pt-PT': 'Marketing'
|
||||
},
|
||||
科学: {
|
||||
'el-GR': 'Επιστήμη',
|
||||
'en-US': 'Science',
|
||||
'es-ES': 'Ciencia',
|
||||
'fr-FR': 'Science',
|
||||
'zh-CN': '科学',
|
||||
'zh-TW': '科學',
|
||||
'ru-RU': 'Наука',
|
||||
'ja-JP': '科学'
|
||||
'ja-JP': '科学',
|
||||
'pt-PT': 'Ciência'
|
||||
},
|
||||
分析: {
|
||||
'el-GR': 'Ανάλυση',
|
||||
'en-US': 'Analysis',
|
||||
'es-ES': 'Análisis',
|
||||
'fr-FR': 'Analyse',
|
||||
'zh-CN': '分析',
|
||||
'zh-TW': '分析',
|
||||
'ru-RU': 'Анализ',
|
||||
'ja-JP': '分析'
|
||||
'ja-JP': '分析',
|
||||
'pt-PT': 'Análise'
|
||||
},
|
||||
法律: {
|
||||
'el-GR': 'Νόμος',
|
||||
'en-US': 'Legal',
|
||||
'es-ES': 'Legal',
|
||||
'fr-FR': 'Légal',
|
||||
'zh-CN': '法律',
|
||||
'zh-TW': '法律',
|
||||
'ru-RU': 'Право',
|
||||
'ja-JP': '法律'
|
||||
'ja-JP': '法律',
|
||||
'pt-PT': 'Legal'
|
||||
},
|
||||
咨询: {
|
||||
'el-GR': 'Συμβουλή',
|
||||
'en-US': 'Consulting',
|
||||
'es-ES': 'Consultoría',
|
||||
'fr-FR': 'Consultation',
|
||||
'zh-CN': '咨询',
|
||||
'zh-TW': '諮詢',
|
||||
'ru-RU': 'Консалтинг',
|
||||
'ja-JP': 'コンサルティング'
|
||||
'ja-JP': 'コンサルティング',
|
||||
'pt-PT': 'Consultoria'
|
||||
},
|
||||
金融: {
|
||||
'el-GR': 'Φορολογία',
|
||||
'en-US': 'Finance',
|
||||
'es-ES': 'Finanzas',
|
||||
'fr-FR': 'Finance',
|
||||
'zh-CN': '金融',
|
||||
'zh-TW': '金融',
|
||||
'ru-RU': 'Финансы',
|
||||
'ja-JP': '金融'
|
||||
'ja-JP': '金融',
|
||||
'pt-PT': 'Finanças'
|
||||
},
|
||||
旅游: {
|
||||
'el-GR': 'Τουρισμός',
|
||||
'en-US': 'Travel',
|
||||
'es-ES': 'Viajes',
|
||||
'fr-FR': 'Voyages',
|
||||
'zh-CN': '旅游',
|
||||
'zh-TW': '旅遊',
|
||||
'ru-RU': 'Путешествия',
|
||||
'ja-JP': '旅行'
|
||||
'ja-JP': '旅行',
|
||||
'pt-PT': 'Viagens'
|
||||
},
|
||||
管理: {
|
||||
'el-GR': 'Διοίκηση',
|
||||
'en-US': 'Management',
|
||||
'es-ES': 'Gestión',
|
||||
'fr-FR': 'Gestion',
|
||||
'zh-CN': '管理',
|
||||
'zh-TW': '管理',
|
||||
'ru-RU': 'Управление',
|
||||
'ja-JP': '管理'
|
||||
'ja-JP': '管理',
|
||||
'pt-PT': 'Gestão'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { PictureOutlined } from '@ant-design/icons'
|
||||
import { isGenerateImageModel } from '@renderer/config/models'
|
||||
import { Assistant, Model } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
model: Model
|
||||
ToolbarButton: any
|
||||
onEnableGenerateImage: () => void
|
||||
}
|
||||
|
||||
const GenerateImageButton: FC<Props> = ({ model, ToolbarButton, assistant, onEnableGenerateImage }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!isGenerateImageModel(model)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
isGenerateImageModel(model) ? t('chat.input.generate_image') : t('chat.input.generate_image_not_supported')
|
||||
}
|
||||
arrow>
|
||||
<ToolbarButton type="text" disabled={!isGenerateImageModel(model)} onClick={onEnableGenerateImage}>
|
||||
<PictureOutlined style={{ color: assistant.enableGenerateImage ? 'var(--color-link)' : 'var(--color-icon)' }} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default GenerateImageButton
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isFunctionCallingModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import { isFunctionCallingModel, isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||
@@ -44,6 +44,7 @@ import styled from 'styled-components'
|
||||
import NarrowLayout from '../Messages/NarrowLayout'
|
||||
import AttachmentButton from './AttachmentButton'
|
||||
import AttachmentPreview from './AttachmentPreview'
|
||||
import GenerateImageButton from './GenerateImageButton'
|
||||
import KnowledgeBaseButton from './KnowledgeBaseButton'
|
||||
import MCPToolsButton from './MCPToolsButton'
|
||||
import MentionModelsButton from './MentionModelsButton'
|
||||
@@ -626,7 +627,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
}
|
||||
|
||||
const onEnableWebSearch = () => {
|
||||
console.log(assistant)
|
||||
if (!isWebSearchModel(model)) {
|
||||
if (!WebSearchService.isWebSearchEnabled()) {
|
||||
window.modal.confirm({
|
||||
@@ -645,10 +645,17 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })
|
||||
}
|
||||
|
||||
const onEnableGenerateImage = () => {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: !assistant.enableGenerateImage })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWebSearchModel(model) && !WebSearchService.isWebSearchEnabled() && assistant.enableWebSearch) {
|
||||
updateAssistant({ ...assistant, enableWebSearch: false })
|
||||
}
|
||||
if (!isGenerateImageModel(model) && assistant.enableGenerateImage) {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: false })
|
||||
}
|
||||
}, [assistant, model, updateAssistant])
|
||||
|
||||
const resetHeight = () => {
|
||||
@@ -738,6 +745,12 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
ToolbarButton={ToolbarButton}
|
||||
/>
|
||||
)}
|
||||
<GenerateImageButton
|
||||
model={model}
|
||||
assistant={assistant}
|
||||
onEnableGenerateImage={onEnableGenerateImage}
|
||||
ToolbarButton={ToolbarButton}
|
||||
/>
|
||||
<MentionModelsButton
|
||||
mentionModels={mentionModels}
|
||||
onMentionModel={(model) => onMentionModel(model, mentionFromKeyboard)}
|
||||
|
||||
@@ -135,7 +135,7 @@ const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
|
||||
|
||||
messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
},
|
||||
[messages, setSelectedMessage]
|
||||
[setSelectedMessage]
|
||||
)
|
||||
|
||||
if (messages.length === 0) return null
|
||||
|
||||
@@ -16,6 +16,7 @@ import styled from 'styled-components'
|
||||
import Markdown from '../Markdown/Markdown'
|
||||
import MessageAttachments from './MessageAttachments'
|
||||
import MessageError from './MessageError'
|
||||
import MessageImage from './MessageImage'
|
||||
import MessageSearchResults from './MessageSearchResults'
|
||||
import MessageThought from './MessageThought'
|
||||
import MessageTools from './MessageTools'
|
||||
@@ -150,6 +151,7 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
||||
<MessageThought message={message} />
|
||||
<MessageTools message={message} />
|
||||
<Markdown message={{ ...message, content: processedContent }} citationsData={citationsData} />
|
||||
{message.metadata?.generateImage && <MessageImage message={message} />}
|
||||
{message.translatedContent && (
|
||||
<Fragment>
|
||||
<Divider style={{ margin: 0, marginBottom: 10 }}>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Message } from '@renderer/types'
|
||||
import { Image as AntdImage } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
}
|
||||
|
||||
const MessageImage: FC<Props> = ({ message }) => {
|
||||
return (
|
||||
<Container style={{ marginBottom: 8 }}>
|
||||
{message.metadata?.generateImage!.images.map((image, index) => (
|
||||
<Image src={image} key={`image-${index}`} width="33%" />
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
const Image = styled(AntdImage)`
|
||||
border-radius: 10px;
|
||||
`
|
||||
|
||||
export default MessageImage
|
||||
@@ -15,8 +15,8 @@ import { UploadOutlined } from '@ant-design/icons'
|
||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
||||
import { isReasoningModel } from '@renderer/config/models'
|
||||
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
||||
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService'
|
||||
@@ -24,7 +24,6 @@ import { translateText } from '@renderer/services/TranslateService'
|
||||
import { Message, Model } from '@renderer/types'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils'
|
||||
import { withMessageThought } from '@renderer/utils/formats'
|
||||
import {
|
||||
exportMarkdownToJoplin,
|
||||
exportMarkdownToNotion,
|
||||
@@ -32,12 +31,13 @@ import {
|
||||
exportMessageAsMarkdown,
|
||||
messageToMarkdown
|
||||
} from '@renderer/utils/export'
|
||||
import { withMessageThought } from '@renderer/utils/formats'
|
||||
import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { clone } from 'lodash'
|
||||
import { FC, memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import { clone } from 'lodash'
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
@@ -242,8 +242,8 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
key: 'obsidian',
|
||||
onClick: async () => {
|
||||
const markdown = messageToMarkdown(message)
|
||||
const title = getMessageTitle(message)
|
||||
await ObsidianExportPopup.show({ title, markdown })
|
||||
const title = topic.name?.replace(/\//g, '_') || 'Untitled'
|
||||
await ObsidianExportPopup.show({ title, markdown, processingMethod: '1' })
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -258,7 +258,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
]
|
||||
}
|
||||
],
|
||||
[message, messageContainerRef, onEdit, onNewBranch, t]
|
||||
[message, messageContainerRef, onEdit, onNewBranch, t, topic.name]
|
||||
)
|
||||
|
||||
const onRegenerate = async (e: React.MouseEvent | undefined) => {
|
||||
|
||||
@@ -26,8 +26,8 @@ import BeatLoader from 'react-spinners/BeatLoader'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ChatNavigation from './ChatNavigation'
|
||||
import MessageGroup from './MessageGroup'
|
||||
import MessageAnchorLine from './MessageAnchorLine'
|
||||
import MessageGroup from './MessageGroup'
|
||||
import NarrowLayout from './NarrowLayout'
|
||||
import NewTopicButton from './NewTopicButton'
|
||||
import Prompt from './Prompt'
|
||||
@@ -139,7 +139,13 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
|
||||
EventEmitter.on(EVENT_NAMES.NEW_BRANCH, async (index: number) => {
|
||||
const newTopic = getDefaultTopic(assistant.id)
|
||||
newTopic.name = topic.name
|
||||
const branchMessages = take(messages, messages.length - index)
|
||||
const currentMessages = messagesRef.current
|
||||
|
||||
// 复制消息并且更新 topicId
|
||||
const branchMessages = take(currentMessages, currentMessages.length - index).map((msg) => ({
|
||||
...msg,
|
||||
topicId: newTopic.id
|
||||
}))
|
||||
|
||||
// 将分支的消息放入数据库
|
||||
await db.topics.add({ id: newTopic.id, messages: branchMessages })
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
setFontSize,
|
||||
setMathEngine,
|
||||
setMessageFont,
|
||||
setMessageNavigation,
|
||||
setMessageStyle,
|
||||
setMultiModelMessageStyle,
|
||||
setPasteLongTextAsFile,
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
setRenderInputMessageAsMarkdown,
|
||||
setShowInputEstimatedTokens,
|
||||
setShowMessageDivider,
|
||||
setMessageNavigation,
|
||||
setThoughtAutoCollapse
|
||||
} from '@renderer/store/settings'
|
||||
import { Assistant, AssistantSettings, CodeStyleVarious, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
||||
|
||||
@@ -262,7 +262,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
key: 'obsidian',
|
||||
onClick: async () => {
|
||||
const markdown = await topicToMarkdown(topic)
|
||||
await ObsidianExportPopup.show({ title: topic.name, markdown })
|
||||
await ObsidianExportPopup.show({ title: topic.name, markdown, processingMethod: '3' })
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ import styled from 'styled-components'
|
||||
import KnowledgeSearchPopup from './components/KnowledgeSearchPopup'
|
||||
import KnowledgeSettings from './components/KnowledgeSettings'
|
||||
import StatusIcon from './components/StatusIcon'
|
||||
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
|
||||
|
||||
const { Dragger } = Upload
|
||||
const { Title } = Typography
|
||||
@@ -62,6 +63,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
} = useKnowledge(selectedBase.id || '')
|
||||
|
||||
const providerName = getProviderName(base?.model.provider || '')
|
||||
const rerankModelProviderName = getProviderName(base?.rerankModel?.provider || '')
|
||||
const disabled = !base?.version || !providerName
|
||||
|
||||
if (!base) {
|
||||
@@ -458,13 +460,34 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
</ContentSection>
|
||||
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
|
||||
<ModelInfo>
|
||||
<label htmlFor="model-info">{t('knowledge.model_info')}</label>
|
||||
<Tag color="blue">{base.model.name}</Tag>
|
||||
<Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag>
|
||||
{providerName && <Tag color="purple">{providerName}</Tag>}
|
||||
<Button icon={<SettingOutlined />} onClick={() => KnowledgeSettings.show({ base })} size="small" />
|
||||
<div className="model-header">
|
||||
<label>{t('knowledge.model_info')}</label>
|
||||
<Button icon={<SettingOutlined />} onClick={() => KnowledgeSettingsPopup.show({ base })} size="small" />
|
||||
</div>
|
||||
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.embedding_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{providerName && <Tag color="purple">{providerName}</Tag>}
|
||||
<Tag color="blue">{base.model.name}</Tag>
|
||||
<Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{base.rerankModel && (
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.rerank_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{rerankModelProviderName && <Tag color="purple">{rerankModelProviderName}</Tag>}
|
||||
<Tag color="blue">{base.rerankModel?.name}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ModelInfo>
|
||||
|
||||
<IndexSection>
|
||||
@@ -560,15 +583,39 @@ const IndexSection = styled.div`
|
||||
|
||||
const ModelInfo = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 5px;
|
||||
color: var(--color-text-3);
|
||||
|
||||
.model-header {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.model-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.label-column {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tag-column {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 8px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
`
|
||||
|
||||
const FlexColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import { SUPPORTED_REANK_PROVIDERS } from '@renderer/config/providers'
|
||||
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { SettingHelpText } from '@renderer/pages/settings'
|
||||
import AiProvider from '@renderer/providers/AiProvider'
|
||||
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
@@ -34,14 +36,17 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
const { t } = useTranslation()
|
||||
const { providers } = useProviders()
|
||||
const { addKnowledgeBase } = useKnowledgeBases()
|
||||
|
||||
const allModels = providers
|
||||
.map((p) => p.models)
|
||||
.flat()
|
||||
.filter((model) => isEmbeddingModel(model))
|
||||
.filter((model) => isEmbeddingModel(model) && !isRerankModel(model))
|
||||
|
||||
const rerankModels = providers
|
||||
.map((p) => p.models)
|
||||
.flat()
|
||||
.filter((model) => isRerankModel(model))
|
||||
|
||||
const nameInputRef = useRef<any>(null)
|
||||
|
||||
const selectOptions = providers
|
||||
@@ -50,7 +55,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||
title: p.name,
|
||||
options: sortBy(p.models, 'name')
|
||||
.filter((model) => isEmbeddingModel(model))
|
||||
.filter((model) => isEmbeddingModel(model) && !isRerankModel(model))
|
||||
.map((m) => ({
|
||||
label: m.name,
|
||||
value: getModelUniqId(m)
|
||||
@@ -60,6 +65,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
|
||||
const rerankSelectOptions = providers
|
||||
.filter((p) => p.models.length > 0)
|
||||
.filter((p) => SUPPORTED_REANK_PROVIDERS.includes(p.id))
|
||||
.map((p) => ({
|
||||
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||
title: p.name,
|
||||
@@ -76,6 +82,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
const selectedModel = find(allModels, JSON.parse(values.model)) as Model
|
||||
|
||||
const selectedRerankModel = values.rerankModel
|
||||
? (find(rerankModels, JSON.parse(values.rerankModel)) as Model)
|
||||
: undefined
|
||||
@@ -165,6 +172,11 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
rules={[{ required: false, message: t('message.error.enter.model') }]}>
|
||||
<Select style={{ width: '100%' }} options={rerankSelectOptions} placeholder={t('settings.models.empty')} />
|
||||
</Form.Item>
|
||||
<SettingHelpText style={{ marginTop: -15, marginBottom: 20 }}>
|
||||
{t('models.rerank_model_support_provider', {
|
||||
provider: SUPPORTED_REANK_PROVIDERS.map((id) => t(`provider.${id}`))
|
||||
})}
|
||||
</SettingHelpText>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -3,8 +3,10 @@ import { TopView } from '@renderer/components/TopView'
|
||||
import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT } from '@renderer/config/constant'
|
||||
import { getEmbeddingMaxContext } from '@renderer/config/embedings'
|
||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import { SUPPORTED_REANK_PROVIDERS } from '@renderer/config/providers'
|
||||
import { useKnowledge } from '@renderer/hooks/useKnowledge'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { SettingHelpText } from '@renderer/pages/settings'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { KnowledgeBase } from '@renderer/types'
|
||||
import { Alert, Form, Input, InputNumber, Modal, Select, Slider } from 'antd'
|
||||
@@ -55,7 +57,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
|
||||
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||
title: p.name,
|
||||
options: sortBy(p.models, 'name')
|
||||
.filter((model) => isEmbeddingModel(model))
|
||||
.filter((model) => isEmbeddingModel(model) && !isRerankModel(model))
|
||||
.map((m) => ({
|
||||
label: m.name,
|
||||
value: getModelUniqId(m)
|
||||
@@ -65,6 +67,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
|
||||
|
||||
const rerankSelectOptions = providers
|
||||
.filter((p) => p.models.length > 0)
|
||||
.filter((p) => SUPPORTED_REANK_PROVIDERS.includes(p.id))
|
||||
.map((p) => ({
|
||||
label: p.isSystem ? t(`provider.${p.id}`) : p.name,
|
||||
title: p.name,
|
||||
@@ -94,7 +97,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
|
||||
}
|
||||
updateKnowledgeBase(newBase)
|
||||
setOpen(false)
|
||||
resolve(newBase)
|
||||
setTimeout(() => resolve(newBase), 350)
|
||||
} catch (error) {
|
||||
console.error('Validation failed:', error)
|
||||
}
|
||||
@@ -151,6 +154,11 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<SettingHelpText style={{ marginTop: -15, marginBottom: 20 }}>
|
||||
{t('models.rerank_model_support_provider', {
|
||||
provider: SUPPORTED_REANK_PROVIDERS.map((id) => t(`provider.${id}`))
|
||||
})}
|
||||
</SettingHelpText>
|
||||
|
||||
<Form.Item
|
||||
name="documentCount"
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import MinApp from '@renderer/components/MinApp'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { RootState, useAppDispatch } from '@renderer/store'
|
||||
import { setObsidianApiKey, setObsidianUrl } from '@renderer/store/settings'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { setObsidianFolder, setObsidianTages, setObsidianValut } from '@renderer/store/settings'
|
||||
import Input from 'antd/es/input/Input'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -17,62 +14,35 @@ const ObsidianSettings: FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const obsidianApiKey = useSelector((state: RootState) => state.settings.obsidianApiKey)
|
||||
const obsidianUrl = useSelector((state: RootState) => state.settings.obsidianUrl)
|
||||
// const obsidianApiKey = useSelector((state: RootState) => state.settings.obsidianApiKey)
|
||||
// const obsidianUrl = useSelector((state: RootState) => state.settings.obsidianUrl)
|
||||
|
||||
const handleObsidianApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianApiKey(e.target.value))
|
||||
const obsidianVault = useSelector((state: RootState) => state.settings.obsidianValut)
|
||||
const obsidianFolder = useSelector((state: RootState) => state.settings.obsidianFolder)
|
||||
const obsidianTags = useSelector((state: RootState) => state.settings.obsidianTages)
|
||||
|
||||
const handleObsidianVaultChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianValut(e.target.value))
|
||||
}
|
||||
|
||||
const handleObsidianUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianUrl(e.target.value))
|
||||
const handleObsidianFolderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianFolder(e.target.value))
|
||||
}
|
||||
|
||||
const handleObsidianUrlBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
let url = e.target.value
|
||||
// 确保URL以/结尾,但只在失去焦点时执行
|
||||
if (url && !url.endsWith('/')) {
|
||||
url = `${url}/`
|
||||
dispatch(setObsidianUrl(url))
|
||||
}
|
||||
const handleObsidianVaultBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianValut(e.target.value))
|
||||
}
|
||||
|
||||
const handleObsidianConnectionCheck = async () => {
|
||||
try {
|
||||
if (!obsidianApiKey) {
|
||||
window.message.error(t('settings.data.obsidian.check.empty_api_key'))
|
||||
return
|
||||
}
|
||||
if (!obsidianUrl) {
|
||||
window.message.error(t('settings.data.obsidian.check.empty_url'))
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch(`${obsidianUrl}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${obsidianApiKey}`
|
||||
}
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok || !data?.authenticated) {
|
||||
window.message.error(t('settings.data.obsidian.check.fail'))
|
||||
return
|
||||
}
|
||||
|
||||
window.message.success(t('settings.data.obsidian.check.success'))
|
||||
} catch (e) {
|
||||
window.message.error(t('settings.data.obsidian.check.fail'))
|
||||
}
|
||||
const handleObsidianFolderBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianFolder(e.target.value))
|
||||
}
|
||||
|
||||
const handleObsidianHelpClick = () => {
|
||||
MinApp.start({
|
||||
id: 'obsidian-help',
|
||||
name: 'Obsidian Help',
|
||||
url: 'https://github.com/coddingtonbear/obsidian-local-rest-api'
|
||||
})
|
||||
const handleObsidianTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianTages(e.target.value))
|
||||
}
|
||||
|
||||
const handleObsidianTagsBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
dispatch(setObsidianTages(e.target.value))
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -80,38 +50,46 @@ const ObsidianSettings: FC = () => {
|
||||
<SettingTitle>{t('settings.data.obsidian.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.data.obsidian.url')}</SettingRowTitle>
|
||||
<SettingRowTitle>{t('settings.data.obsidian.vault')}</SettingRowTitle>
|
||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
||||
<Input
|
||||
type="text"
|
||||
value={obsidianUrl || ''}
|
||||
onChange={handleObsidianUrlChange}
|
||||
onBlur={handleObsidianUrlBlur}
|
||||
value={obsidianVault || ''}
|
||||
onChange={handleObsidianVaultChange}
|
||||
onBlur={handleObsidianVaultBlur}
|
||||
style={{ width: 315 }}
|
||||
placeholder={t('settings.data.obsidian.url_placeholder')}
|
||||
placeholder={t('settings.data.obsidian.vault_placeholder')}
|
||||
/>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{t('settings.data.obsidian.api_key')}</span>
|
||||
<Tooltip title={t('settings.data.obsidian.help')} placement="left">
|
||||
<InfoCircleOutlined
|
||||
style={{ color: 'var(--color-text-2)', cursor: 'pointer', marginLeft: 4 }}
|
||||
onClick={handleObsidianHelpClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
<span>{t('settings.data.obsidian.folder')}</span>
|
||||
</SettingRowTitle>
|
||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
||||
<Input
|
||||
type="password"
|
||||
value={obsidianApiKey || ''}
|
||||
onChange={handleObsidianApiKeyChange}
|
||||
style={{ width: 250 }}
|
||||
placeholder={t('settings.data.obsidian.api_key_placeholder')}
|
||||
value={obsidianFolder || ''}
|
||||
onChange={handleObsidianFolderChange}
|
||||
onBlur={handleObsidianFolderBlur}
|
||||
style={{ width: 315 }}
|
||||
placeholder={t('settings.data.obsidian.folder_placeholder')}
|
||||
/>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{t('settings.data.obsidian.tags')}</span>
|
||||
</SettingRowTitle>
|
||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
||||
<Input
|
||||
value={obsidianTags || ''}
|
||||
onChange={handleObsidianTagsChange}
|
||||
onBlur={handleObsidianTagsBlur}
|
||||
style={{ width: 315 }}
|
||||
placeholder={t('settings.data.obsidian.tags_placeholder')}
|
||||
/>
|
||||
<Button onClick={handleObsidianConnectionCheck}>{t('settings.data.obsidian.check.button')}</Button>
|
||||
</HStack>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
|
||||
@@ -12,13 +12,13 @@ import {
|
||||
setWebdavSyncInterval as _setWebdavSyncInterval,
|
||||
setWebdavUser as _setWebdavUser
|
||||
} from '@renderer/store/settings'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { Button, Input, Modal, Select, Spin, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
|
||||
interface BackupFile {
|
||||
fileName: string
|
||||
|
||||
@@ -13,13 +13,47 @@ import { useTranslation } from 'react-i18next'
|
||||
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '.'
|
||||
|
||||
const GeneralSettings: FC = () => {
|
||||
const { language, proxyUrl: storeProxyUrl, theme, setTray, tray, proxyMode: storeProxyMode } = useSettings()
|
||||
const {
|
||||
language,
|
||||
proxyUrl: storeProxyUrl,
|
||||
theme,
|
||||
setLaunch,
|
||||
setTray,
|
||||
launchOnBoot,
|
||||
launchToTray,
|
||||
trayOnClose,
|
||||
tray,
|
||||
proxyMode: storeProxyMode
|
||||
} = useSettings()
|
||||
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
|
||||
const { theme: themeMode } = useTheme()
|
||||
|
||||
const updateTray = (value: boolean) => {
|
||||
setTray(value)
|
||||
window.api.setTray(value)
|
||||
const updateTray = (isShowTray: boolean) => {
|
||||
setTray(isShowTray)
|
||||
//only set tray on close/launch to tray when tray is enabled
|
||||
if (!isShowTray) {
|
||||
updateTrayOnClose(false)
|
||||
updateLaunchToTray(false)
|
||||
}
|
||||
}
|
||||
|
||||
const updateTrayOnClose = (isTrayOnClose: boolean) => {
|
||||
setTray(undefined, isTrayOnClose)
|
||||
//in case tray is not enabled, enable it
|
||||
if (isTrayOnClose && !tray) {
|
||||
updateTray(true)
|
||||
}
|
||||
}
|
||||
|
||||
const updateLaunchOnBoot = (isLaunchOnBoot: boolean) => {
|
||||
setLaunch(isLaunchOnBoot)
|
||||
}
|
||||
|
||||
const updateLaunchToTray = (isLaunchToTray: boolean) => {
|
||||
setLaunch(undefined, isLaunchToTray)
|
||||
if (isLaunchToTray && !tray) {
|
||||
updateTray(true)
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -52,8 +86,10 @@ const GeneralSettings: FC = () => {
|
||||
dispatch(setProxyMode(mode))
|
||||
if (mode === 'system') {
|
||||
window.api.setProxy('system')
|
||||
dispatch(_setProxyUrl(undefined))
|
||||
} else if (mode === 'none') {
|
||||
window.api.setProxy(undefined)
|
||||
dispatch(_setProxyUrl(undefined))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +98,11 @@ const GeneralSettings: FC = () => {
|
||||
{ value: 'zh-TW', label: '中文(繁体)', flag: '🇭🇰' },
|
||||
{ value: 'en-US', label: 'English', flag: '🇺🇸' },
|
||||
{ value: 'ja-JP', label: '日本語', flag: '🇯🇵' },
|
||||
{ value: 'ru-RU', label: 'Русский', flag: '🇷🇺' }
|
||||
{ value: 'ru-RU', label: 'Русский', flag: '🇷🇺' },
|
||||
{ value: 'el-GR', label: 'Ελληνικά', flag: '🇬🇷' },
|
||||
{ value: 'es-ES', label: 'Español', flag: '🇪🇸' },
|
||||
{ value: 'fr-FR', label: 'Français', flag: '🇫🇷' },
|
||||
{ value: 'pt-PT', label: 'Português', flag: '🇵🇹' }
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -111,11 +151,32 @@ const GeneralSettings: FC = () => {
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>{t('settings.launch.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.tray.title')}</SettingRowTitle>
|
||||
<SettingRowTitle>{t('settings.launch.onboot')}</SettingRowTitle>
|
||||
<Switch checked={launchOnBoot} onChange={(checked) => updateLaunchOnBoot(checked)} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.launch.totray')}</SettingRowTitle>
|
||||
<Switch checked={launchToTray} onChange={(checked) => updateLaunchToTray(checked)} />
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>{t('settings.tray.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.tray.show')}</SettingRowTitle>
|
||||
<Switch checked={tray} onChange={(checked) => updateTray(checked)} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.tray.onclose')}</SettingRowTitle>
|
||||
<Switch checked={trayOnClose} onChange={(checked) => updateTrayOnClose(checked)} disabled={!tray} />
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
</SettingContainer>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import type { MCPServer } from '@renderer/types'
|
||||
import { Button, Input, Space, Spin, Table, Typography } from 'antd'
|
||||
import { npxFinder } from 'npx-scope-finder'
|
||||
import { FC, useState } from 'react'
|
||||
import { type FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingTitle } from '..'
|
||||
@@ -21,7 +21,7 @@ interface SearchResult {
|
||||
const NpxSearch: FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation()
|
||||
const { Paragraph, Text } = Typography
|
||||
const { Paragraph, Text, Link } = Typography
|
||||
|
||||
// Add new state variables for npm scope search
|
||||
const [npmScope, setNpmScope] = useState('@modelcontextprotocol')
|
||||
@@ -59,8 +59,12 @@ const NpxSearch: FC = () => {
|
||||
if (formattedResults.length === 0) {
|
||||
window.message.info(t('settings.mcp.npx_list.no_packages'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
window.message.error(`${t('settings.mcp.npx_list.search_error')}: ${error.message}`)
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
window.message.error(`${t('settings.mcp.npx_list.search_error')}: ${error.message}`)
|
||||
} else {
|
||||
window.message.error(t('settings.mcp.npx_list.search_error'))
|
||||
}
|
||||
} finally {
|
||||
setSearchLoading(false)
|
||||
}
|
||||
@@ -92,7 +96,7 @@ const NpxSearch: FC = () => {
|
||||
<Spin />
|
||||
</div>
|
||||
) : searchResults.length > 0 ? (
|
||||
<Table<SearchResult>
|
||||
<Table
|
||||
dataSource={searchResults}
|
||||
columns={[
|
||||
{
|
||||
@@ -104,15 +108,18 @@ const NpxSearch: FC = () => {
|
||||
{
|
||||
title: t('settings.mcp.npx_list.description'),
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
render: (_, record: SearchResult) => (
|
||||
<Space direction="vertical" size="small">
|
||||
<Text>{record.description}</Text>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<Text ellipsis={{ tooltip: true }}>{record.description}</Text>
|
||||
<Text ellipsis={{ tooltip: true }} type="secondary">
|
||||
{t('settings.mcp.npx_list.usage')}: {record.usage}
|
||||
</Text>
|
||||
<a href={record.npmLink} target="_blank" rel="noopener noreferrer" style={{ fontSize: '12px' }}>
|
||||
{record.npmLink}
|
||||
</a>
|
||||
<Paragraph ellipsis={{ tooltip: true }}>
|
||||
<Link href={record.npmLink} target="_blank" rel="noopener noreferrer">
|
||||
{record.npmLink}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
@@ -125,7 +132,7 @@ const NpxSearch: FC = () => {
|
||||
{
|
||||
title: t('settings.mcp.npx_list.actions'),
|
||||
key: 'actions',
|
||||
width: '100px',
|
||||
width: '120px',
|
||||
render: (_, record: SearchResult) => (
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -149,7 +156,6 @@ const NpxSearch: FC = () => {
|
||||
}
|
||||
]}
|
||||
pagination={false}
|
||||
size="small"
|
||||
bordered
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isEmbeddingModel,
|
||||
isFunctionCallingModel,
|
||||
isReasoningModel,
|
||||
isRerankModel,
|
||||
isVisionModel,
|
||||
isWebSearchModel,
|
||||
SYSTEM_MODELS
|
||||
@@ -17,7 +18,7 @@ import { getDefaultGroupName, isFreeModel, runAsyncFunction } from '@renderer/ut
|
||||
import { Avatar, Button, Empty, Flex, Modal, Popover, Radio, Tooltip } from 'antd'
|
||||
import Search from 'antd/es/input/Search'
|
||||
import { groupBy, isEmpty, uniqBy } from 'lodash'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -44,6 +45,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [filterType, setFilterType] = useState<string>('all')
|
||||
const { t, i18n } = useTranslation()
|
||||
const searchInputRef = useRef<any>(null)
|
||||
|
||||
const systemModels = SYSTEM_MODELS[_provider.id] || []
|
||||
const allModels = uniqBy([...systemModels, ...listModels, ...models], 'id')
|
||||
@@ -70,6 +72,8 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
return isEmbeddingModel(model)
|
||||
case 'function_calling':
|
||||
return isFunctionCallingModel(model)
|
||||
case 'rerank':
|
||||
return isRerankModel(model)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@@ -127,6 +131,14 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open && searchInputRef.current) {
|
||||
setTimeout(() => {
|
||||
searchInputRef.current?.focus()
|
||||
}, 100)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const ModalHeader = () => {
|
||||
return (
|
||||
<Flex>
|
||||
@@ -148,7 +160,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
footer={null}
|
||||
width="600px"
|
||||
width="680px"
|
||||
styles={{
|
||||
content: { padding: 0 },
|
||||
header: { padding: 22, paddingBottom: 15 }
|
||||
@@ -156,17 +168,23 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
centered>
|
||||
<SearchContainer>
|
||||
<Center>
|
||||
<Radio.Group value={filterType} onChange={(e) => setFilterType(e.target.value)} buttonStyle="solid">
|
||||
<Radio.Group
|
||||
size={i18n.language.startsWith('zh') ? 'middle' : 'small'}
|
||||
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>
|
||||
<Radio.Button value="embedding">{t('models.embedding')}</Radio.Button>
|
||||
<Radio.Button value="function_calling">{t('models.function_calling')}</Radio.Button>
|
||||
<Radio.Button value="reasoning">{t('models.type.reasoning')}</Radio.Button>
|
||||
<Radio.Button value="vision">{t('models.type.vision')}</Radio.Button>
|
||||
<Radio.Button value="websearch">{t('models.type.websearch')}</Radio.Button>
|
||||
<Radio.Button value="free">{t('models.type.free')}</Radio.Button>
|
||||
<Radio.Button value="embedding">{t('models.type.embedding')}</Radio.Button>
|
||||
<Radio.Button value="rerank">{t('models.type.rerank')}</Radio.Button>
|
||||
<Radio.Button value="function_calling">{t('models.type.function_calling')}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Center>
|
||||
<Search
|
||||
ref={searchInputRef}
|
||||
placeholder={t('settings.provider.search_placeholder')}
|
||||
allowClear
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Model, Provider } from '@renderer/types'
|
||||
import { maskApiKey } from '@renderer/utils/api'
|
||||
import { Avatar, Button, Card, Flex, Space, Tooltip, Typography } from 'antd'
|
||||
import { groupBy, sortBy, toPairs } from 'lodash'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -38,6 +38,7 @@ const STATUS_COLORS = {
|
||||
interface ModelListProps {
|
||||
provider: Provider
|
||||
modelStatuses?: ModelStatus[]
|
||||
searchText?: string
|
||||
}
|
||||
|
||||
export interface ModelStatus {
|
||||
@@ -165,7 +166,7 @@ function useModelStatusRendering() {
|
||||
return { renderStatusIndicator, renderLatencyText }
|
||||
}
|
||||
|
||||
const ModelList: React.FC<ModelListProps> = ({ provider: _provider, modelStatuses = [] }) => {
|
||||
const ModelList: React.FC<ModelListProps> = ({ provider: _provider, modelStatuses = [], searchText = '' }) => {
|
||||
const { t } = useTranslation()
|
||||
const { provider } = useProvider(_provider.id)
|
||||
const { updateProvider, models, removeModel } = useProvider(_provider.id)
|
||||
@@ -179,7 +180,21 @@ const ModelList: React.FC<ModelListProps> = ({ provider: _provider, modelStatuse
|
||||
const modelsWebsite = providerConfig?.websites?.models
|
||||
|
||||
const [editingModel, setEditingModel] = useState<Model | null>(null)
|
||||
const modelGroups = groupBy(models, 'group')
|
||||
const [debouncedSearchText, setDebouncedSearchText] = useState(searchText)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchText(searchText)
|
||||
}, 50)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [searchText])
|
||||
|
||||
const filteredModels = debouncedSearchText
|
||||
? models.filter((model) => model.name.toLowerCase().includes(debouncedSearchText.toLowerCase()))
|
||||
: models
|
||||
|
||||
const modelGroups = groupBy(filteredModels, 'group')
|
||||
const sortedModelGroups = sortBy(toPairs(modelGroups), [0]).reduce((acc, [key, value]) => {
|
||||
acc[key] = value
|
||||
return acc
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import { Input, Tooltip } from 'antd'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface ModelListSearchBarProps {
|
||||
onSearch: (text: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* A collapsible search bar for the model list
|
||||
* Renders as an icon initially, expands to full search input when clicked
|
||||
*/
|
||||
const ModelListSearchBar: React.FC<ModelListSearchBarProps> = ({ onSearch }) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchVisible, setSearchVisible] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
const handleTextChange = (text: string) => {
|
||||
setSearchText(text)
|
||||
onSearch(text)
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
setSearchText('')
|
||||
setSearchVisible(false)
|
||||
onSearch('')
|
||||
}
|
||||
|
||||
return searchVisible ? (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('models.search')}
|
||||
size="small"
|
||||
style={{ width: '160px' }}
|
||||
suffix={<SearchOutlined style={{ color: 'var(--color-text-3)' }} />}
|
||||
onChange={(e) => handleTextChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
handleTextChange('')
|
||||
if (!searchText) setSearchVisible(false)
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!searchText) setSearchVisible(false)
|
||||
}}
|
||||
autoFocus
|
||||
allowClear
|
||||
onClear={handleClear}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip title={t('models.search')} mouseEnterDelay={0.5}>
|
||||
<SearchOutlined onClick={() => setSearchVisible(true)} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelListSearchBar
|
||||
@@ -12,7 +12,7 @@ import { isProviderSupportAuth, isProviderSupportCharge } from '@renderer/servic
|
||||
import { Provider } from '@renderer/types'
|
||||
import { formatApiHost } from '@renderer/utils/api'
|
||||
import { providerCharge } from '@renderer/utils/oauth'
|
||||
import { Button, Divider, Flex, Input, Space, Switch } from 'antd'
|
||||
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
|
||||
import Link from 'antd/es/typography/Link'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
@@ -34,6 +34,7 @@ import GraphRAGSettings from './GraphRAGSettings'
|
||||
import HealthCheckPopup from './HealthCheckPopup'
|
||||
import LMStudioSettings from './LMStudioSettings'
|
||||
import ModelList, { ModelStatus } from './ModelList'
|
||||
import ModelListSearchBar from './ModelListSearchBar'
|
||||
import OllamSettings from './OllamaSettings'
|
||||
import ProviderSettingsPopup from './ProviderSettingsPopup'
|
||||
import SelectProviderModelPopup from './SelectProviderModelPopup'
|
||||
@@ -49,6 +50,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
||||
const [apiValid, setApiValid] = useState(false)
|
||||
const [apiChecking, setApiChecking] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const { updateProvider, models } = useProvider(provider.id)
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
@@ -267,9 +269,11 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
</Link>
|
||||
)}
|
||||
{!provider.isSystem && (
|
||||
<Button type="text" style={{ width: 30 }} onClick={() => ProviderSettingsPopup.show({ provider })}>
|
||||
<SettingOutlined />
|
||||
</Button>
|
||||
<SettingOutlined
|
||||
type="text"
|
||||
style={{ width: 30 }}
|
||||
onClick={() => ProviderSettingsPopup.show({ provider })}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Switch
|
||||
@@ -359,22 +363,25 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
)}
|
||||
{provider.id === 'copilot' && <GithubCopilotSettings provider={provider} setApiKey={setApiKey} />}
|
||||
<SettingSubtitle style={{ marginBottom: 5 }}>
|
||||
<Flex align="center" justify="space-between" style={{ width: '100%' }}>
|
||||
<span>{t('common.models')}</span>
|
||||
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Space>
|
||||
{!isEmpty(models) && (
|
||||
<span>{t('common.models')}</span>
|
||||
{!isEmpty(models) && <ModelListSearchBar onSearch={setSearchText} />}
|
||||
</Space>
|
||||
{!isEmpty(models) && (
|
||||
<Tooltip title={t('settings.models.check.button_caption')} mouseEnterDelay={0.5}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<HeartOutlined />}
|
||||
onClick={onHealthCheck}
|
||||
loading={isHealthChecking}
|
||||
title={t('settings.models.check.button_caption')}></Button>
|
||||
)}
|
||||
</Space>
|
||||
</Flex>
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</SettingSubtitle>
|
||||
<ModelList provider={provider} modelStatuses={modelStatuses} />
|
||||
<ModelList provider={provider} modelStatuses={modelStatuses} searchText={searchText} />
|
||||
</SettingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
ContentListUnion,
|
||||
createPartFromBase64,
|
||||
FinishReason,
|
||||
GenerateContentResponse,
|
||||
GoogleGenAI
|
||||
} from '@google/genai'
|
||||
import {
|
||||
Content,
|
||||
FileDataPart,
|
||||
@@ -35,16 +42,19 @@ import axios from 'axios'
|
||||
import { isEmpty, takeRight } from 'lodash'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
import { CompletionsParams } from '.'
|
||||
import { ChunkCallbackData, CompletionsParams } from '.'
|
||||
import BaseProvider from './BaseProvider'
|
||||
|
||||
export default class GeminiProvider extends BaseProvider {
|
||||
private sdk: GoogleGenerativeAI
|
||||
private requestOptions: RequestOptions
|
||||
private imageSdk: GoogleGenAI
|
||||
|
||||
constructor(provider: Provider) {
|
||||
super(provider)
|
||||
this.sdk = new GoogleGenerativeAI(this.apiKey)
|
||||
/// this sdk is experimental
|
||||
this.imageSdk = new GoogleGenAI({ apiKey: this.apiKey })
|
||||
this.requestOptions = {
|
||||
baseUrl: this.getBaseURL()
|
||||
}
|
||||
@@ -105,6 +115,25 @@ export default class GeminiProvider extends BaseProvider {
|
||||
const role = message.role === 'user' ? 'user' : 'model'
|
||||
|
||||
const parts: Part[] = [{ text: await this.getMessageContent(message) }]
|
||||
// Add any generated images from previous responses
|
||||
if (message.metadata?.generateImage?.images && message.metadata.generateImage.images.length > 0) {
|
||||
for (const imageUrl of message.metadata.generateImage.images) {
|
||||
if (imageUrl && imageUrl.startsWith('data:')) {
|
||||
// Extract base64 data and mime type from the data URL
|
||||
const matches = imageUrl.match(/^data:(.+);base64,(.*)$/)
|
||||
if (matches && matches.length === 3) {
|
||||
const mimeType = matches[1]
|
||||
const base64Data = matches[2]
|
||||
parts.push({
|
||||
inlineData: {
|
||||
data: base64Data,
|
||||
mimeType: mimeType
|
||||
}
|
||||
} as InlineDataPart)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of message.files || []) {
|
||||
if (file.type === FileTypes.IMAGE) {
|
||||
@@ -179,180 +208,184 @@ export default class GeminiProvider extends BaseProvider {
|
||||
* @param onFilterMessages - The onFilterMessages callback
|
||||
*/
|
||||
public async completions({ messages, assistant, mcpTools, onChunk, onFilterMessages }: CompletionsParams) {
|
||||
const defaultModel = getDefaultModel()
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
|
||||
if (assistant.enableGenerateImage) {
|
||||
await this.generateImageExp({ messages, assistant, onFilterMessages, onChunk })
|
||||
} else {
|
||||
const defaultModel = getDefaultModel()
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
|
||||
|
||||
const userMessages = filterUserRoleStartMessages(
|
||||
filterEmptyMessages(filterContextMessages(takeRight(messages, contextCount + 2)))
|
||||
)
|
||||
onFilterMessages(userMessages)
|
||||
const userMessages = filterUserRoleStartMessages(
|
||||
filterEmptyMessages(filterContextMessages(takeRight(messages, contextCount + 2)))
|
||||
)
|
||||
onFilterMessages(userMessages)
|
||||
|
||||
const userLastMessage = userMessages.pop()
|
||||
const userLastMessage = userMessages.pop()
|
||||
|
||||
const history: Content[] = []
|
||||
const history: Content[] = []
|
||||
|
||||
for (const message of userMessages) {
|
||||
history.push(await this.getMessageContents(message))
|
||||
}
|
||||
|
||||
const tools = mcpToolsToGeminiTools(mcpTools)
|
||||
const toolResponses: MCPToolResponse[] = []
|
||||
|
||||
if (assistant.enableWebSearch && isWebSearchModel(model)) {
|
||||
tools.push({
|
||||
// @ts-ignore googleSearch is not a valid tool for Gemini
|
||||
googleSearch: {}
|
||||
})
|
||||
}
|
||||
|
||||
const geminiModel = this.sdk.getGenerativeModel(
|
||||
{
|
||||
model: model.id,
|
||||
...(isGemmaModel(model) ? {} : { systemInstruction: assistant.prompt }),
|
||||
safetySettings: this.getSafetySettings(model.id),
|
||||
tools: tools,
|
||||
generationConfig: {
|
||||
maxOutputTokens: maxTokens,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
topP: assistant?.settings?.topP,
|
||||
...this.getCustomParameters(assistant)
|
||||
}
|
||||
},
|
||||
this.requestOptions
|
||||
)
|
||||
|
||||
const chat = geminiModel.startChat({ history })
|
||||
const messageContents = await this.getMessageContents(userLastMessage!)
|
||||
|
||||
if (isGemmaModel(model) && assistant.prompt) {
|
||||
const isFirstMessage = history.length === 0
|
||||
if (isFirstMessage) {
|
||||
const systemMessage = {
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
text:
|
||||
'<start_of_turn>user\n' +
|
||||
assistant.prompt +
|
||||
'<end_of_turn>\n' +
|
||||
'<start_of_turn>user\n' +
|
||||
messageContents.parts[0].text +
|
||||
'<end_of_turn>'
|
||||
}
|
||||
]
|
||||
}
|
||||
messageContents.parts = systemMessage.parts
|
||||
for (const message of userMessages) {
|
||||
history.push(await this.getMessageContents(message))
|
||||
}
|
||||
}
|
||||
|
||||
const start_time_millsec = new Date().getTime()
|
||||
const { abortController, cleanup } = this.createAbortController(userLastMessage?.id)
|
||||
const { signal } = abortController
|
||||
const tools = mcpToolsToGeminiTools(mcpTools)
|
||||
const toolResponses: MCPToolResponse[] = []
|
||||
|
||||
if (!streamOutput) {
|
||||
const { response } = await chat.sendMessage(messageContents.parts, { signal })
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
onChunk({
|
||||
text: response.candidates?.[0].content.parts[0].text,
|
||||
usage: {
|
||||
prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: response.usageMetadata?.totalTokenCount || 0
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount,
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec: 0
|
||||
},
|
||||
search: response.candidates?.[0]?.groundingMetadata
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const userMessagesStream = await chat.sendMessageStream(messageContents.parts, { signal })
|
||||
let time_first_token_millsec = 0
|
||||
|
||||
const processStream = async (stream: GenerateContentStreamResult, idx: number) => {
|
||||
for await (const chunk of stream.stream) {
|
||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
||||
|
||||
if (time_first_token_millsec == 0) {
|
||||
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||
}
|
||||
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
|
||||
const functionCalls = chunk.functionCalls()
|
||||
|
||||
if (functionCalls) {
|
||||
const fcallParts: FunctionCallPart[] = []
|
||||
const fcRespParts: FunctionResponsePart[] = []
|
||||
for (const call of functionCalls) {
|
||||
console.log('Function call:', call)
|
||||
fcallParts.push({ functionCall: call } as FunctionCallPart)
|
||||
const mcpTool = geminiFunctionCallToMcpTool(mcpTools, call)
|
||||
if (mcpTool) {
|
||||
upsertMCPToolResponse(
|
||||
toolResponses,
|
||||
{
|
||||
tool: mcpTool,
|
||||
status: 'invoking',
|
||||
id: `${call.name}-${idx}`
|
||||
},
|
||||
onChunk
|
||||
)
|
||||
const toolCallResponse = await callMCPTool(mcpTool)
|
||||
fcRespParts.push({
|
||||
functionResponse: {
|
||||
name: mcpTool.id,
|
||||
response: toolCallResponse
|
||||
}
|
||||
})
|
||||
upsertMCPToolResponse(
|
||||
toolResponses,
|
||||
{
|
||||
tool: mcpTool,
|
||||
status: 'done',
|
||||
response: toolCallResponse,
|
||||
id: `${call.name}-${idx}`
|
||||
},
|
||||
onChunk
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (fcRespParts) {
|
||||
history.push(messageContents)
|
||||
history.push({
|
||||
role: 'model',
|
||||
parts: fcallParts
|
||||
})
|
||||
const newChat = geminiModel.startChat({ history })
|
||||
const newStream = await newChat.sendMessageStream(fcRespParts, { signal })
|
||||
await processStream(newStream, idx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
onChunk({
|
||||
text: chunk.text(),
|
||||
usage: {
|
||||
prompt_tokens: chunk.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: chunk.usageMetadata?.totalTokenCount || 0
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount,
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec
|
||||
},
|
||||
search: chunk.candidates?.[0]?.groundingMetadata,
|
||||
mcpToolResponse: toolResponses
|
||||
if (assistant.enableWebSearch && isWebSearchModel(model)) {
|
||||
tools.push({
|
||||
// @ts-ignore googleSearch is not a valid tool for Gemini
|
||||
googleSearch: {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await processStream(userMessagesStream, 0).finally(cleanup)
|
||||
const geminiModel = this.sdk.getGenerativeModel(
|
||||
{
|
||||
model: model.id,
|
||||
...(isGemmaModel(model) ? {} : { systemInstruction: assistant.prompt }),
|
||||
safetySettings: this.getSafetySettings(model.id),
|
||||
tools: tools,
|
||||
generationConfig: {
|
||||
maxOutputTokens: maxTokens,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
topP: assistant?.settings?.topP,
|
||||
...this.getCustomParameters(assistant)
|
||||
}
|
||||
},
|
||||
this.requestOptions
|
||||
)
|
||||
|
||||
const chat = geminiModel.startChat({ history })
|
||||
const messageContents = await this.getMessageContents(userLastMessage!)
|
||||
|
||||
if (isGemmaModel(model) && assistant.prompt) {
|
||||
const isFirstMessage = history.length === 0
|
||||
if (isFirstMessage) {
|
||||
const systemMessage = {
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
text:
|
||||
'<start_of_turn>user\n' +
|
||||
assistant.prompt +
|
||||
'<end_of_turn>\n' +
|
||||
'<start_of_turn>user\n' +
|
||||
messageContents.parts[0].text +
|
||||
'<end_of_turn>'
|
||||
}
|
||||
]
|
||||
}
|
||||
messageContents.parts = systemMessage.parts
|
||||
}
|
||||
}
|
||||
|
||||
const start_time_millsec = new Date().getTime()
|
||||
const { abortController, cleanup } = this.createAbortController(userLastMessage?.id)
|
||||
const { signal } = abortController
|
||||
|
||||
if (!streamOutput) {
|
||||
const { response } = await chat.sendMessage(messageContents.parts, { signal })
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
onChunk({
|
||||
text: response.candidates?.[0].content.parts[0].text,
|
||||
usage: {
|
||||
prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: response.usageMetadata?.totalTokenCount || 0
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount,
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec: 0
|
||||
},
|
||||
search: response.candidates?.[0]?.groundingMetadata
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const userMessagesStream = await chat.sendMessageStream(messageContents.parts, { signal })
|
||||
let time_first_token_millsec = 0
|
||||
|
||||
const processStream = async (stream: GenerateContentStreamResult, idx: number) => {
|
||||
for await (const chunk of stream.stream) {
|
||||
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) break
|
||||
|
||||
if (time_first_token_millsec == 0) {
|
||||
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||
}
|
||||
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
|
||||
const functionCalls = chunk.functionCalls()
|
||||
|
||||
if (functionCalls) {
|
||||
const fcallParts: FunctionCallPart[] = []
|
||||
const fcRespParts: FunctionResponsePart[] = []
|
||||
for (const call of functionCalls) {
|
||||
console.log('Function call:', call)
|
||||
fcallParts.push({ functionCall: call } as FunctionCallPart)
|
||||
const mcpTool = geminiFunctionCallToMcpTool(mcpTools, call)
|
||||
if (mcpTool) {
|
||||
upsertMCPToolResponse(
|
||||
toolResponses,
|
||||
{
|
||||
tool: mcpTool,
|
||||
status: 'invoking',
|
||||
id: `${call.name}-${idx}`
|
||||
},
|
||||
onChunk
|
||||
)
|
||||
const toolCallResponse = await callMCPTool(mcpTool)
|
||||
fcRespParts.push({
|
||||
functionResponse: {
|
||||
name: mcpTool.id,
|
||||
response: toolCallResponse
|
||||
}
|
||||
})
|
||||
upsertMCPToolResponse(
|
||||
toolResponses,
|
||||
{
|
||||
tool: mcpTool,
|
||||
status: 'done',
|
||||
response: toolCallResponse,
|
||||
id: `${call.name}-${idx}`
|
||||
},
|
||||
onChunk
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (fcRespParts) {
|
||||
history.push(messageContents)
|
||||
history.push({
|
||||
role: 'model',
|
||||
parts: fcallParts
|
||||
})
|
||||
const newChat = geminiModel.startChat({ history })
|
||||
const newStream = await newChat.sendMessageStream(fcRespParts, { signal })
|
||||
await processStream(newStream, idx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
onChunk({
|
||||
text: chunk.text(),
|
||||
usage: {
|
||||
prompt_tokens: chunk.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: chunk.usageMetadata?.totalTokenCount || 0
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: chunk.usageMetadata?.candidatesTokenCount,
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec
|
||||
},
|
||||
search: chunk.candidates?.[0]?.groundingMetadata,
|
||||
mcpToolResponse: toolResponses
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await processStream(userMessagesStream, 0).finally(cleanup)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,6 +569,150 @@ export default class GeminiProvider extends BaseProvider {
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图像
|
||||
* @param messages - 消息列表
|
||||
* @param assistant - 助手配置
|
||||
* @param onChunk - 处理生成块的回调
|
||||
* @param onFilterMessages - 过滤消息的回调
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
private async generateImageExp({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams): Promise<void> {
|
||||
const defaultModel = getDefaultModel()
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount } = getAssistantSettings(assistant)
|
||||
|
||||
const userMessages = filterUserRoleStartMessages(filterContextMessages(takeRight(messages, contextCount + 2)))
|
||||
onFilterMessages(userMessages)
|
||||
|
||||
const userLastMessage = userMessages.pop()
|
||||
if (!userLastMessage) {
|
||||
throw new Error('No user message found')
|
||||
}
|
||||
|
||||
const history: Content[] = []
|
||||
|
||||
for (const message of userMessages) {
|
||||
history.push(await this.getMessageContents(message))
|
||||
}
|
||||
|
||||
const userLastMessageContent = await this.getMessageContents(userLastMessage)
|
||||
const allContents = [...history, userLastMessageContent]
|
||||
|
||||
let contents: ContentListUnion = allContents.length > 0 ? (allContents as ContentListUnion) : []
|
||||
|
||||
contents = await this.addImageFileToContents(userLastMessage, contents)
|
||||
|
||||
const response = await this.callGeminiGenerateContent(model.id, contents)
|
||||
|
||||
console.log('response', response)
|
||||
|
||||
const { isValid, message } = this.isValidGeminiResponse(response)
|
||||
if (!isValid) {
|
||||
throw new Error(`Gemini API error: ${message}`)
|
||||
}
|
||||
|
||||
this.processGeminiImageResponse(response, onChunk)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加图片文件到内容列表
|
||||
* @param message - 用户消息
|
||||
* @param contents - 内容列表
|
||||
* @returns 更新后的内容列表
|
||||
*/
|
||||
private async addImageFileToContents(message: Message, contents: ContentListUnion): Promise<ContentListUnion> {
|
||||
if (message.files && message.files.length > 0) {
|
||||
const file = message.files[0]
|
||||
const fileContent = await window.api.file.base64Image(file.id + file.ext)
|
||||
|
||||
if (fileContent && fileContent.base64) {
|
||||
const contentsArray = Array.isArray(contents) ? contents : [contents]
|
||||
return [...contentsArray, createPartFromBase64(fileContent.base64, fileContent.mime)]
|
||||
}
|
||||
}
|
||||
return contents
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用Gemini API生成内容
|
||||
* @param modelId - 模型ID
|
||||
* @param contents - 内容列表
|
||||
* @returns 生成结果
|
||||
*/
|
||||
private async callGeminiGenerateContent(
|
||||
modelId: string,
|
||||
contents: ContentListUnion
|
||||
): Promise<GenerateContentResponse> {
|
||||
try {
|
||||
return await this.imageSdk.models.generateContent({
|
||||
model: modelId,
|
||||
contents: contents,
|
||||
config: {
|
||||
responseModalities: ['Text', 'Image'],
|
||||
responseMimeType: 'text/plain'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Gemini API error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Gemini响应是否有效
|
||||
* @param response - Gemini响应
|
||||
* @returns 是否有效
|
||||
*/
|
||||
private isValidGeminiResponse(response: GenerateContentResponse): { isValid: boolean; message: string } {
|
||||
return {
|
||||
isValid: response?.candidates?.[0]?.finishReason === FinishReason.STOP ? true : false,
|
||||
message: response?.candidates?.[0]?.finishReason || ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Gemini图像响应
|
||||
* @param response - Gemini响应
|
||||
* @param onChunk - 处理生成块的回调
|
||||
*/
|
||||
private processGeminiImageResponse(response: any, onChunk: (chunk: ChunkCallbackData) => void): void {
|
||||
const parts = response.candidates[0].content.parts
|
||||
|
||||
// 提取图像数据
|
||||
const images = parts
|
||||
.filter((part: Part) => part.inlineData)
|
||||
.map((part: Part) => {
|
||||
if (!part.inlineData) {
|
||||
return null
|
||||
}
|
||||
const dataPrefix = `data:${part.inlineData.mimeType || 'image/png'};base64,`
|
||||
return part.inlineData.data.startsWith('data:') ? part.inlineData.data : dataPrefix + part.inlineData.data
|
||||
})
|
||||
|
||||
// 提取文本数据
|
||||
const text = parts
|
||||
.filter((part: Part) => part.text !== undefined)
|
||||
.map((part: Part) => part.text)
|
||||
.join('')
|
||||
|
||||
// 返回结果
|
||||
onChunk({
|
||||
text,
|
||||
generateImage: {
|
||||
images
|
||||
},
|
||||
usage: {
|
||||
prompt_tokens: response.usageMetadata?.promptTokenCount || 0,
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount || 0,
|
||||
total_tokens: response.usageMetadata?.totalTokenCount || 0
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: response.usageMetadata?.candidatesTokenCount
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model is valid
|
||||
* @param model - The model
|
||||
|
||||
@@ -474,7 +474,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
|
||||
const finishReason = chunk.choices[0]?.finish_reason
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
if (delta?.tool_calls?.length) {
|
||||
const chunkToolCalls = delta.tool_calls
|
||||
for (const t of chunkToolCalls) {
|
||||
const { index, id, function: fn, type } = t
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ interface ChunkCallbackData {
|
||||
search?: GroundingMetadata
|
||||
citations?: string[]
|
||||
mcpToolResponse?: MCPToolResponse[]
|
||||
generateImage?: GenerateImageResponse
|
||||
}
|
||||
|
||||
interface CompletionsParams {
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function fetchChatCompletion({
|
||||
messages: filterUsefulMessages(messages),
|
||||
assistant,
|
||||
onFilterMessages: (messages) => (_messages = messages),
|
||||
onChunk: ({ text, reasoning_content, usage, metrics, search, citations, mcpToolResponse }) => {
|
||||
onChunk: ({ text, reasoning_content, usage, metrics, search, citations, mcpToolResponse, generateImage }) => {
|
||||
message.content = message.content + text || ''
|
||||
message.usage = usage
|
||||
message.metrics = metrics
|
||||
@@ -127,6 +127,12 @@ export async function fetchChatCompletion({
|
||||
if (mcpToolResponse) {
|
||||
message.metadata = { ...message.metadata, mcpTools: cloneDeep(mcpToolResponse) }
|
||||
}
|
||||
if (generateImage) {
|
||||
message.metadata = {
|
||||
...message.metadata,
|
||||
generateImage: generateImage
|
||||
}
|
||||
}
|
||||
|
||||
// Handle citations from Perplexity API
|
||||
if (isFirstChunk && citations) {
|
||||
@@ -162,6 +168,7 @@ export async function fetchChatCompletion({
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('message', message)
|
||||
} catch (error: any) {
|
||||
if (isAbortError(error)) {
|
||||
message.status = 'paused'
|
||||
|
||||
@@ -800,6 +800,9 @@ const migrateConfig = {
|
||||
},
|
||||
'83': (state: RootState) => {
|
||||
state.settings.messageNavigation = 'buttons'
|
||||
state.settings.launchOnBoot = false
|
||||
state.settings.launchToTray = false
|
||||
state.settings.trayOnClose = true
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ export interface SettingsState {
|
||||
showMessageDivider: boolean
|
||||
messageFont: 'system' | 'serif'
|
||||
showInputEstimatedTokens: boolean
|
||||
launchOnBoot: boolean
|
||||
launchToTray: boolean
|
||||
trayOnClose: boolean
|
||||
tray: boolean
|
||||
theme: ThemeMode
|
||||
windowStyle: 'transparent' | 'opaque'
|
||||
@@ -83,8 +86,10 @@ export interface SettingsState {
|
||||
yuqueToken: string | null
|
||||
yuqueUrl: string | null
|
||||
yuqueRepoId: string | null
|
||||
obsidianApiKey: string | null
|
||||
obsidianUrl: string | null
|
||||
//obsidian settings obsidianVault, obisidanFolder
|
||||
obsidianValut: string | null
|
||||
obsidianFolder: string | null
|
||||
obsidianTages: string | null
|
||||
joplinToken: string | null
|
||||
joplinUrl: string | null
|
||||
}
|
||||
@@ -103,6 +108,9 @@ const initialState: SettingsState = {
|
||||
showMessageDivider: true,
|
||||
messageFont: 'system',
|
||||
showInputEstimatedTokens: false,
|
||||
launchOnBoot: false,
|
||||
launchToTray: false,
|
||||
trayOnClose: true,
|
||||
tray: true,
|
||||
theme: ThemeMode.auto,
|
||||
windowStyle: 'transparent',
|
||||
@@ -155,8 +163,9 @@ const initialState: SettingsState = {
|
||||
yuqueToken: '',
|
||||
yuqueUrl: '',
|
||||
yuqueRepoId: '',
|
||||
obsidianApiKey: '',
|
||||
obsidianUrl: '',
|
||||
obsidianValut: '',
|
||||
obsidianFolder: '',
|
||||
obsidianTages: '',
|
||||
joplinToken: '',
|
||||
joplinUrl: ''
|
||||
}
|
||||
@@ -205,9 +214,18 @@ const settingsSlice = createSlice({
|
||||
setShowInputEstimatedTokens: (state, action: PayloadAction<boolean>) => {
|
||||
state.showInputEstimatedTokens = action.payload
|
||||
},
|
||||
setLaunchOnBoot: (state, action: PayloadAction<boolean>) => {
|
||||
state.launchOnBoot = action.payload
|
||||
},
|
||||
setLaunchToTray: (state, action: PayloadAction<boolean>) => {
|
||||
state.launchToTray = action.payload
|
||||
},
|
||||
setTray: (state, action: PayloadAction<boolean>) => {
|
||||
state.tray = action.payload
|
||||
},
|
||||
setTrayOnClose: (state, action: PayloadAction<boolean>) => {
|
||||
state.trayOnClose = action.payload
|
||||
},
|
||||
setTheme: (state, action: PayloadAction<ThemeMode>) => {
|
||||
state.theme = action.payload
|
||||
},
|
||||
@@ -354,11 +372,14 @@ const settingsSlice = createSlice({
|
||||
setYuqueUrl: (state, action: PayloadAction<string>) => {
|
||||
state.yuqueUrl = action.payload
|
||||
},
|
||||
setObsidianApiKey: (state, action: PayloadAction<string>) => {
|
||||
state.obsidianApiKey = action.payload
|
||||
setObsidianValut: (state, action: PayloadAction<string>) => {
|
||||
state.obsidianValut = action.payload
|
||||
},
|
||||
setObsidianUrl: (state, action: PayloadAction<string>) => {
|
||||
state.obsidianUrl = action.payload
|
||||
setObsidianFolder: (state, action: PayloadAction<string>) => {
|
||||
state.obsidianFolder = action.payload
|
||||
},
|
||||
setObsidianTages: (state, action: PayloadAction<string>) => {
|
||||
state.obsidianTages = action.payload
|
||||
},
|
||||
setJoplinToken: (state, action: PayloadAction<string>) => {
|
||||
state.joplinToken = action.payload
|
||||
@@ -386,6 +407,9 @@ export const {
|
||||
setShowMessageDivider,
|
||||
setMessageFont,
|
||||
setShowInputEstimatedTokens,
|
||||
setLaunchOnBoot,
|
||||
setLaunchToTray,
|
||||
setTrayOnClose,
|
||||
setTray,
|
||||
setTheme,
|
||||
setFontSize,
|
||||
@@ -434,8 +458,9 @@ export const {
|
||||
setYuqueToken,
|
||||
setYuqueRepoId,
|
||||
setYuqueUrl,
|
||||
setObsidianApiKey,
|
||||
setObsidianUrl,
|
||||
setObsidianValut,
|
||||
setObsidianFolder,
|
||||
setObsidianTages,
|
||||
setJoplinToken,
|
||||
setJoplinUrl,
|
||||
setMessageNavigation
|
||||
|
||||
@@ -16,6 +16,7 @@ export type Assistant = {
|
||||
settings?: Partial<AssistantSettings>
|
||||
messages?: AssistantMessage[]
|
||||
enableWebSearch?: boolean
|
||||
enableGenerateImage?: boolean
|
||||
}
|
||||
|
||||
export type AssistantMessage = {
|
||||
@@ -77,6 +78,8 @@ export type Message = {
|
||||
webSearch?: WebSearchResponse
|
||||
// MCP Tools
|
||||
mcpTools?: MCPToolResponse[]
|
||||
// Generate Image
|
||||
generateImage?: GenerateImageResponse
|
||||
}
|
||||
// 多模型消息样式
|
||||
multiModelMessageStyle?: 'horizontal' | 'vertical' | 'fold' | 'grid'
|
||||
@@ -196,9 +199,18 @@ export enum ThemeMode {
|
||||
auto = 'auto'
|
||||
}
|
||||
|
||||
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'ja-JP'
|
||||
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU'
|
||||
|
||||
export type TranslateLanguageVarious = 'chinese' | 'chinese-traditional' | 'english' | 'japanese' | 'russian'
|
||||
export type TranslateLanguageVarious =
|
||||
| 'chinese'
|
||||
| 'chinese-traditional'
|
||||
| 'greek'
|
||||
| 'english'
|
||||
| 'spanish'
|
||||
| 'french'
|
||||
| 'japanese'
|
||||
| 'portuguese'
|
||||
| 'russian'
|
||||
|
||||
export type CodeStyleVarious = BuiltinTheme | 'auto'
|
||||
|
||||
@@ -306,6 +318,10 @@ export type GenerateImageParams = {
|
||||
promptEnhancement?: boolean
|
||||
}
|
||||
|
||||
export type GenerateImageResponse = {
|
||||
images: string[]
|
||||
}
|
||||
|
||||
export interface TranslateHistory {
|
||||
id: string
|
||||
sourceText: string
|
||||
|
||||
@@ -320,58 +320,46 @@ export const exportMarkdownToYuque = async (title: string, content: string) => {
|
||||
|
||||
/**
|
||||
* 导出Markdown到Obsidian
|
||||
* @param attributes 文档属性
|
||||
* @param attributes.title 标题
|
||||
* @param attributes.created 创建时间
|
||||
* @param attributes.source 来源
|
||||
* @param attributes.tags 标签
|
||||
* @param attributes.processingMethod 处理方式
|
||||
*/
|
||||
export const exportMarkdownToObsidian = async (
|
||||
fileName: string,
|
||||
markdown: string,
|
||||
selectedPath: string,
|
||||
isMdFile: boolean = false
|
||||
) => {
|
||||
export const exportMarkdownToObsidian = async (attributes: any) => {
|
||||
try {
|
||||
const obsidianUrl = store.getState().settings.obsidianUrl
|
||||
const obsidianApiKey = store.getState().settings.obsidianApiKey
|
||||
const obsidianValut = store.getState().settings.obsidianValut
|
||||
const obsidianFolder = store.getState().settings.obsidianFolder
|
||||
|
||||
if (!obsidianUrl || !obsidianApiKey) {
|
||||
if (!obsidianValut || !obsidianFolder) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_not_configured'))
|
||||
return
|
||||
}
|
||||
let path = ''
|
||||
|
||||
// 如果是md文件,直接将内容追加到该文件
|
||||
if (isMdFile) {
|
||||
const response = await fetch(`${obsidianUrl}vault${selectedPath}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown',
|
||||
Authorization: `Bearer ${obsidianApiKey}`
|
||||
},
|
||||
body: `\n\n${markdown}` // 添加两个换行后追加内容
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 创建新文件
|
||||
const sanitizedFileName = removeSpecialCharactersForFileName(fileName)
|
||||
const path = selectedPath === '/' ? '' : selectedPath
|
||||
const fullPath = path.endsWith('/') ? `${path}${sanitizedFileName}.md` : `${path}/${sanitizedFileName}.md`
|
||||
|
||||
const response = await fetch(`${obsidianUrl}vault${fullPath}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown',
|
||||
Authorization: `Bearer ${obsidianApiKey}`
|
||||
},
|
||||
body: markdown
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
|
||||
return
|
||||
}
|
||||
if (!attributes.title) {
|
||||
window.message.error(i18n.t('chat.topics.export.obsidian_title_required'))
|
||||
return
|
||||
}
|
||||
|
||||
//构建保存路径添加以 / 结尾
|
||||
if (!obsidianFolder.endsWith('/')) {
|
||||
path = obsidianFolder + '/'
|
||||
}
|
||||
//构建文件名
|
||||
const fileName = transformObsidianFileName(attributes.title)
|
||||
|
||||
let obsidianUrl = `obsidian://new?file=${encodeURIComponent(path + fileName)}&vault=${encodeURIComponent(obsidianValut)}&clipboard`
|
||||
|
||||
if (attributes.processingMethod === '3') {
|
||||
obsidianUrl += '&overwrite=true'
|
||||
} else if (attributes.processingMethod === '2') {
|
||||
obsidianUrl += '&prepend=true'
|
||||
} else if (attributes.processingMethod === '1') {
|
||||
obsidianUrl += '&append=true'
|
||||
}
|
||||
window.open(obsidianUrl)
|
||||
window.message.success(i18n.t('chat.topics.export.obsidian_export_success'))
|
||||
} catch (error) {
|
||||
console.error('导出到Obsidian失败:', error)
|
||||
@@ -379,6 +367,51 @@ export const exportMarkdownToObsidian = async (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Obsidian文件名,源自 Obsidian Web Clipper 官方实现,修改了一些细节
|
||||
* @param fileName
|
||||
* @returns
|
||||
*/
|
||||
|
||||
function transformObsidianFileName(fileName: string): string {
|
||||
const platform = window.navigator.userAgent
|
||||
const isWindows = /win/i.test(platform)
|
||||
const isMac = /mac/i.test(platform)
|
||||
|
||||
// 删除Obsidian 全平台无效字符
|
||||
let sanitized = fileName.replace(/[#|\\^\\[\]]/g, '')
|
||||
|
||||
if (isWindows) {
|
||||
// Windows 的清理
|
||||
sanitized = sanitized
|
||||
.replace(/[<>:"\\/\\|?*]/g, '') // 移除无效字符
|
||||
.replace(/^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i, '_$1$2') // 避免保留名称
|
||||
.replace(/[\s.]+$/, '') // 移除结尾的空格和句点
|
||||
} else if (isMac) {
|
||||
// Mac 的清理
|
||||
sanitized = sanitized
|
||||
.replace(/[/:\u0020-\u007E]/g, '') // 移除无效字符
|
||||
.replace(/^\./, '_') // 避免以句点开头
|
||||
} else {
|
||||
// Linux 或其他系统
|
||||
sanitized = sanitized
|
||||
.replace(/[<>:"\\/\\|?*]/g, '') // 移除无效字符
|
||||
.replace(/^\./, '_') // 避免以句点开头
|
||||
}
|
||||
|
||||
// 所有平台的通用操作
|
||||
sanitized = sanitized
|
||||
.replace(/^\.+/, '') // 移除开头的句点
|
||||
.trim() // 移除前后空格
|
||||
.slice(0, 245) // 截断为 245 个字符,留出空间以追加 ' 1.md'
|
||||
|
||||
// 确保文件名不为空
|
||||
if (sanitized.length === 0) {
|
||||
sanitized = 'Untitled'
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
export const exportMarkdownToJoplin = async (title: string, content: string) => {
|
||||
const { joplinUrl, joplinToken } = store.getState().settings
|
||||
|
||||
|
||||
@@ -1021,6 +1021,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@google/genai@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "@google/genai@npm:0.4.0"
|
||||
dependencies:
|
||||
google-auth-library: "npm:^9.14.2"
|
||||
ws: "npm:^8.18.0"
|
||||
checksum: 10c0/4feb837b373cdbe60a5388b880b2384b116ffa369ae17ec2562c4e9da0f90e315d5e30c413ee3a620b6d147c55e1e9165f0e143aba6d945f1dfbe61fa584fefc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@google/generative-ai@npm:^0.21.0":
|
||||
version: 0.21.0
|
||||
resolution: "@google/generative-ai@npm:0.21.0"
|
||||
@@ -2638,15 +2648,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:^0.5.11":
|
||||
version: 0.5.15
|
||||
resolution: "@swc/helpers@npm:0.5.15"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@szmarczak/http-timer@npm:^4.0.5":
|
||||
version: 4.0.6
|
||||
resolution: "@szmarczak/http-timer@npm:4.0.6"
|
||||
@@ -2797,20 +2798,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/command-line-args@npm:^5.2.3":
|
||||
version: 5.2.3
|
||||
resolution: "@types/command-line-args@npm:5.2.3"
|
||||
checksum: 10c0/3a9bc58fd26e546391f6369dd28c03d59349dc4ac39eada1a5c39cc3578e02e4aac222615170e0db79b198ffba2af84fdbdda46e08c6edc4da42bc17ea85200f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/command-line-usage@npm:^5.0.4":
|
||||
version: 5.0.4
|
||||
resolution: "@types/command-line-usage@npm:5.0.4"
|
||||
checksum: 10c0/67840ebf4bcfee200c07d978669ad596fe2adc350fd5c19d44ec2248623575d96ec917f513d1d59453f8f57e879133861a4cc41c20045c07f6c959f1fcaac7ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6":
|
||||
version: 4.1.12
|
||||
resolution: "@types/debug@npm:4.1.12"
|
||||
@@ -3018,7 +3005,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^20.13.0, @types/node@npm:^20.9.0":
|
||||
"@types/node@npm:^20.9.0":
|
||||
version: 20.17.24
|
||||
resolution: "@types/node@npm:20.17.24"
|
||||
dependencies:
|
||||
@@ -3343,6 +3330,7 @@ __metadata:
|
||||
"@emotion/is-prop-valid": "npm:^1.3.1"
|
||||
"@eslint-react/eslint-plugin": "npm:^1.36.1"
|
||||
"@eslint/js": "npm:^9.22.0"
|
||||
"@google/genai": "npm:^0.4.0"
|
||||
"@google/generative-ai": "npm:^0.21.0"
|
||||
"@hello-pangea/dnd": "npm:^16.6.0"
|
||||
"@kangfenmao/keyv-storage": "npm:^0.1.0"
|
||||
@@ -3377,7 +3365,6 @@ __metadata:
|
||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
||||
adm-zip: "npm:^0.5.16"
|
||||
antd: "npm:^5.22.5"
|
||||
apache-arrow: "npm:^18.1.0"
|
||||
applescript: "npm:^1.0.0"
|
||||
axios: "npm:^1.7.3"
|
||||
babel-plugin-styled-components: "npm:^2.1.4"
|
||||
@@ -3732,25 +3719,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"apache-arrow@npm:^18.1.0":
|
||||
version: 18.1.0
|
||||
resolution: "apache-arrow@npm:18.1.0"
|
||||
dependencies:
|
||||
"@swc/helpers": "npm:^0.5.11"
|
||||
"@types/command-line-args": "npm:^5.2.3"
|
||||
"@types/command-line-usage": "npm:^5.0.4"
|
||||
"@types/node": "npm:^20.13.0"
|
||||
command-line-args: "npm:^5.2.1"
|
||||
command-line-usage: "npm:^7.0.1"
|
||||
flatbuffers: "npm:^24.3.25"
|
||||
json-bignum: "npm:^0.0.3"
|
||||
tslib: "npm:^2.6.2"
|
||||
bin:
|
||||
arrow2csv: bin/arrow2csv.js
|
||||
checksum: 10c0/2bb43c19e8e29b5cba8eb5a3a76f7e4e93ecb25658e49de2c3997be12a461a72e2a4ddd2f90e6806e9725bb28e391ffedc9e6e0ab1f613eb70cfb5438d2c4d21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"app-builder-bin@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "app-builder-bin@npm:4.0.0"
|
||||
@@ -3865,20 +3833,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-back@npm:^3.0.1, array-back@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "array-back@npm:3.1.0"
|
||||
checksum: 10c0/bb1fe86aa8b39c21e73c68c7abf8b05ed939b8951a3b17527217f6a2a84e00e4cfa4fdec823081689c5e216709bf1f214a4f5feeee6726eaff83897fa1a7b8ee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-back@npm:^6.2.2":
|
||||
version: 6.2.2
|
||||
resolution: "array-back@npm:6.2.2"
|
||||
checksum: 10c0/c98a6e43b669400f58e2fba478336d5d02aac970566ffae3af0cb9b5585ec3811a1e010c76e34fb809a9762e6822a43a9c9a1b99f2a35f43b11a9e198e782818
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-union@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "array-union@npm:2.1.0"
|
||||
@@ -4021,7 +3975,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
|
||||
"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "base64-js@npm:1.5.1"
|
||||
checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
|
||||
@@ -4044,6 +3998,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bignumber.js@npm:^9.0.0":
|
||||
version: 9.1.2
|
||||
resolution: "bignumber.js@npm:9.1.2"
|
||||
checksum: 10c0/e17786545433f3110b868725c449fa9625366a6e675cd70eb39b60938d6adbd0158cb4b3ad4f306ce817165d37e63f4aa3098ba4110db1d9a3b9f66abfbaf10d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bindings@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "bindings@npm:1.5.0"
|
||||
@@ -4210,6 +4171,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-equal-constant-time@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "buffer-equal-constant-time@npm:1.0.1"
|
||||
checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-equal@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "buffer-equal@npm:0.0.1"
|
||||
@@ -4505,15 +4473,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk-template@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "chalk-template@npm:0.4.0"
|
||||
dependencies:
|
||||
chalk: "npm:^4.1.2"
|
||||
checksum: 10c0/6a4cb4252966475f0bd3ee1cd8780146e1ba69f445e59c565cab891ac18708c8143515d23e2b0fb7e192574fb7608d429ea5b28f3b7b9507770ad6fccd3467e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:2.4.2":
|
||||
version: 2.4.2
|
||||
resolution: "chalk@npm:2.4.2"
|
||||
@@ -4787,30 +4746,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"command-line-args@npm:^5.2.1":
|
||||
version: 5.2.1
|
||||
resolution: "command-line-args@npm:5.2.1"
|
||||
dependencies:
|
||||
array-back: "npm:^3.1.0"
|
||||
find-replace: "npm:^3.0.0"
|
||||
lodash.camelcase: "npm:^4.3.0"
|
||||
typical: "npm:^4.0.0"
|
||||
checksum: 10c0/a4f6a23a1e420441bd1e44dee24efd12d2e49af7efe6e21eb32fca4e843ca3d5501ddebad86a4e9d99aa626dd6dcb64c04a43695388be54e3a803dbc326cc89f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"command-line-usage@npm:^7.0.1":
|
||||
version: 7.0.3
|
||||
resolution: "command-line-usage@npm:7.0.3"
|
||||
dependencies:
|
||||
array-back: "npm:^6.2.2"
|
||||
chalk-template: "npm:^0.4.0"
|
||||
table-layout: "npm:^4.1.0"
|
||||
typical: "npm:^7.1.1"
|
||||
checksum: 10c0/444a3e3c6fcbdcb5802de0fd2864ea5aef83eeeb3a825fd24846b996503d4b4140e75aeb2939b3430a06407f3acc02b76b3e08dafb3a3092d22fdcced0ecb0b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:9.2.0":
|
||||
version: 9.2.0
|
||||
resolution: "commander@npm:9.2.0"
|
||||
@@ -5657,6 +5592,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11":
|
||||
version: 1.0.11
|
||||
resolution: "ecdsa-sig-formatter@npm:1.0.11"
|
||||
dependencies:
|
||||
safe-buffer: "npm:^5.0.1"
|
||||
checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ee-first@npm:1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "ee-first@npm:1.1.1"
|
||||
@@ -6672,7 +6616,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"extend@npm:^3.0.0, extend@npm:~3.0.2":
|
||||
"extend@npm:^3.0.0, extend@npm:^3.0.2, extend@npm:~3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "extend@npm:3.0.2"
|
||||
checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9
|
||||
@@ -6937,15 +6881,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-replace@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "find-replace@npm:3.0.0"
|
||||
dependencies:
|
||||
array-back: "npm:^3.0.1"
|
||||
checksum: 10c0/fcd1bf7960388c8193c2861bcdc760c18ac14edb4bde062a961915d9a25727b2e8aabf0229e90cc09c753fd557e5a3e5ae61e49cadbe727be89a9e8e49ce7668
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-up@npm:^1.0.0":
|
||||
version: 1.1.2
|
||||
resolution: "find-up@npm:1.1.2"
|
||||
@@ -6985,13 +6920,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flatbuffers@npm:^24.3.25":
|
||||
version: 24.12.23
|
||||
resolution: "flatbuffers@npm:24.12.23"
|
||||
checksum: 10c0/f6c7e4440c724ee337dac54db1d6ae428e84b2bf6618c542d095956e77b521bdd8a0e4d87dc93b15ae6ed4d07a8b269b5c99fd766e5acbe67546ef81034b1e05
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flatted@npm:^3.2.9":
|
||||
version: 3.3.3
|
||||
resolution: "flatted@npm:3.3.3"
|
||||
@@ -7258,6 +7186,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gaxios@npm:^6.0.0, gaxios@npm:^6.1.1":
|
||||
version: 6.7.1
|
||||
resolution: "gaxios@npm:6.7.1"
|
||||
dependencies:
|
||||
extend: "npm:^3.0.2"
|
||||
https-proxy-agent: "npm:^7.0.1"
|
||||
is-stream: "npm:^2.0.0"
|
||||
node-fetch: "npm:^2.6.9"
|
||||
uuid: "npm:^9.0.1"
|
||||
checksum: 10c0/53e92088470661c5bc493a1de29d05aff58b1f0009ec5e7903f730f892c3642a93e264e61904383741ccbab1ce6e519f12a985bba91e13527678b32ee6d7d3fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gcp-metadata@npm:^6.1.0":
|
||||
version: 6.1.1
|
||||
resolution: "gcp-metadata@npm:6.1.1"
|
||||
dependencies:
|
||||
gaxios: "npm:^6.1.1"
|
||||
google-logging-utils: "npm:^0.0.2"
|
||||
json-bigint: "npm:^1.0.0"
|
||||
checksum: 10c0/71f6ad4800aa622c246ceec3955014c0c78cdcfe025971f9558b9379f4019f5e65772763428ee8c3244fa81b8631977316eaa71a823493f82e5c44d7259ffac8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gensync@npm:^1.0.0-beta.2":
|
||||
version: 1.0.0-beta.2
|
||||
resolution: "gensync@npm:1.0.0-beta.2"
|
||||
@@ -7501,6 +7453,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"google-auth-library@npm:^9.14.2":
|
||||
version: 9.15.1
|
||||
resolution: "google-auth-library@npm:9.15.1"
|
||||
dependencies:
|
||||
base64-js: "npm:^1.3.0"
|
||||
ecdsa-sig-formatter: "npm:^1.0.11"
|
||||
gaxios: "npm:^6.1.1"
|
||||
gcp-metadata: "npm:^6.1.0"
|
||||
gtoken: "npm:^7.0.0"
|
||||
jws: "npm:^4.0.0"
|
||||
checksum: 10c0/6eef36d9a9cb7decd11e920ee892579261c6390104b3b24d3e0f3889096673189fe2ed0ee43fd563710e2560de98e63ad5aa4967b91e7f4e69074a422d5f7b65
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"google-logging-utils@npm:^0.0.2":
|
||||
version: 0.0.2
|
||||
resolution: "google-logging-utils@npm:0.0.2"
|
||||
checksum: 10c0/9a4bbd470dd101c77405e450fffca8592d1d7114f245a121288d04a957aca08c9dea2dd1a871effe71e41540d1bb0494731a0b0f6fea4358e77f06645e4268c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gopd@npm:^1.0.1, gopd@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "gopd@npm:1.2.0"
|
||||
@@ -7560,6 +7533,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gtoken@npm:^7.0.0":
|
||||
version: 7.1.0
|
||||
resolution: "gtoken@npm:7.1.0"
|
||||
dependencies:
|
||||
gaxios: "npm:^6.0.0"
|
||||
jws: "npm:^4.0.0"
|
||||
checksum: 10c0/0a3dcacb1a3c4578abe1ee01c7d0bf20bffe8ded3ee73fc58885d53c00f6eb43b4e1372ff179f0da3ed5cfebd5b7c6ab8ae2776f1787e90d943691b4fe57c716
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"har-schema@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "har-schema@npm:2.0.0"
|
||||
@@ -8495,6 +8478,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-stream@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "is-stream@npm:2.0.1"
|
||||
checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-stream@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "is-stream@npm:3.0.0"
|
||||
@@ -8671,10 +8661,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"json-bignum@npm:^0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "json-bignum@npm:0.0.3"
|
||||
checksum: 10c0/f9f9312d57a68f72676802fa087da4ed60241d73b6cc0e3fb9f587ca0de7364efb62612a14414ccfbedc0b77ce3c320adca21834a5673c99eb3375aef9f561db
|
||||
"json-bigint@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "json-bigint@npm:1.0.0"
|
||||
dependencies:
|
||||
bignumber.js: "npm:^9.0.0"
|
||||
checksum: 10c0/e3f34e43be3284b573ea150a3890c92f06d54d8ded72894556357946aeed9877fd795f62f37fe16509af189fd314ab1104d0fd0f163746ad231b9f378f5b33f4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8822,6 +8814,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jwa@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "jwa@npm:2.0.0"
|
||||
dependencies:
|
||||
buffer-equal-constant-time: "npm:1.0.1"
|
||||
ecdsa-sig-formatter: "npm:1.0.11"
|
||||
safe-buffer: "npm:^5.0.1"
|
||||
checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jws@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "jws@npm:4.0.0"
|
||||
dependencies:
|
||||
jwa: "npm:^2.0.0"
|
||||
safe-buffer: "npm:^5.0.1"
|
||||
checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"katex@npm:^0.12.0":
|
||||
version: 0.12.0
|
||||
resolution: "katex@npm:0.12.0"
|
||||
@@ -9160,13 +9173,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.camelcase@npm:^4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "lodash.camelcase@npm:4.3.0"
|
||||
checksum: 10c0/fcba15d21a458076dd309fce6b1b4bf611d84a0ec252cb92447c948c533ac250b95d2e00955801ebc367e5af5ed288b996d75d37d2035260a937008e14eaf432
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.escaperegexp@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "lodash.escaperegexp@npm:4.1.2"
|
||||
@@ -10812,7 +10818,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7":
|
||||
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
dependencies:
|
||||
@@ -14455,16 +14461,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"table-layout@npm:^4.1.0":
|
||||
version: 4.1.1
|
||||
resolution: "table-layout@npm:4.1.1"
|
||||
dependencies:
|
||||
array-back: "npm:^6.2.2"
|
||||
wordwrapjs: "npm:^5.1.0"
|
||||
checksum: 10c0/26d8e54a55ddb4de447c8f02a8d7fcbb66a9580375e406a3bc7717ab223a413f6dfbded6710f288b3dfd277991813a0bd5a17419a0dc6db54d9a36d883d868dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:^2.0.0":
|
||||
version: 2.1.2
|
||||
resolution: "tar-fs@npm:2.1.2"
|
||||
@@ -14773,7 +14769,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0":
|
||||
"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.6.2":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
@@ -14878,20 +14874,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typical@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "typical@npm:4.0.0"
|
||||
checksum: 10c0/f300b198fb9fe743859b75ec761d53c382723dc178bbce4957d9cb754f2878a44ce17dc0b6a5156c52be1065449271f63754ba594dac225b80ce3aa39f9241ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typical@npm:^7.1.1":
|
||||
version: 7.3.0
|
||||
resolution: "typical@npm:7.3.0"
|
||||
checksum: 10c0/bee697a88e1dd0447bc1cf7f6e875eaa2b0fb2cccb338b7b261e315f7df84a66402864bfc326d6b3117c50475afd1d49eda03d846a6299ad25f211035bfab3b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "uc.micro@npm:2.1.0"
|
||||
@@ -15268,6 +15250,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uuid@npm:^9.0.1":
|
||||
version: 9.0.1
|
||||
resolution: "uuid@npm:9.0.1"
|
||||
bin:
|
||||
uuid: dist/bin/uuid
|
||||
checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uzip@npm:0.20201231.0":
|
||||
version: 0.20201231.0
|
||||
resolution: "uzip@npm:0.20201231.0"
|
||||
@@ -15539,13 +15530,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wordwrapjs@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "wordwrapjs@npm:5.1.0"
|
||||
checksum: 10c0/e147162f139eb8c05257729fde586f5422a2d242aa8f027b5fa5adead1b571b455d0690a15c73aeaa31c93ba96864caa06d84ebdb2c32a0890602ab86a7568d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "wrap-ansi@npm:7.0.0"
|
||||
@@ -15596,7 +15580,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.13.0":
|
||||
"ws@npm:^8.13.0, ws@npm:^8.18.0":
|
||||
version: 8.18.1
|
||||
resolution: "ws@npm:8.18.1"
|
||||
peerDependencies:
|
||||
|
||||
Reference in New Issue
Block a user