Merge branch 'main' into feat-knowlege-ocr
This commit is contained in:
@@ -52,6 +52,8 @@ jobs:
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: develop
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -26,6 +26,8 @@ jobs:
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Get release tag
|
||||
id: get-tag
|
||||
|
||||
@@ -37,3 +37,49 @@ index 7d19f5578040afa004bc887aab1725e8703d2bac..59ec725b6142299a62798ac4bdedb63b
|
||||
if (status === 400) {
|
||||
return new BadRequestError(status, error, message, headers);
|
||||
}
|
||||
diff --git a/resources/embeddings.js b/resources/embeddings.js
|
||||
index aae578404cb2d09a39ac33fc416f1c215c45eecd..25c54b05bdae64d5c3b36fbb30dc7c8221b14034 100644
|
||||
--- a/resources/embeddings.js
|
||||
+++ b/resources/embeddings.js
|
||||
@@ -36,6 +36,9 @@ class Embeddings extends resource_1.APIResource {
|
||||
// No encoding_format specified, defaulting to base64 for performance reasons
|
||||
// See https://github.com/openai/openai-node/pull/1312
|
||||
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
+ if (body.model.includes('jina')) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
if (hasUserProvidedEncodingFormat) {
|
||||
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
|
||||
}
|
||||
@@ -47,7 +50,7 @@ class Embeddings extends resource_1.APIResource {
|
||||
...options,
|
||||
});
|
||||
// if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
|
||||
return response;
|
||||
}
|
||||
// in this stage, we are sure the user did not specify an encoding_format
|
||||
diff --git a/resources/embeddings.mjs b/resources/embeddings.mjs
|
||||
index 0df3c6cc79a520e54acb4c2b5f77c43b774035ff..aa488b8a11b2c413c0a663d9a6059d286d7b5faf 100644
|
||||
--- a/resources/embeddings.mjs
|
||||
+++ b/resources/embeddings.mjs
|
||||
@@ -10,6 +10,9 @@ export class Embeddings extends APIResource {
|
||||
// No encoding_format specified, defaulting to base64 for performance reasons
|
||||
// See https://github.com/openai/openai-node/pull/1312
|
||||
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
+ if (body.model.includes('jina')) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
if (hasUserProvidedEncodingFormat) {
|
||||
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
|
||||
}
|
||||
@@ -21,7 +24,7 @@ export class Embeddings extends APIResource {
|
||||
...options,
|
||||
});
|
||||
// if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
|
||||
return response;
|
||||
}
|
||||
// in this stage, we are sure the user did not specify an encoding_format
|
||||
|
||||
@@ -19,7 +19,7 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai
|
||||
|
||||
# 📖 Guide
|
||||
|
||||
https://docs.cherry-ai.com
|
||||
<https://docs.cherry-ai.com>
|
||||
|
||||
# 🌠 Screenshot
|
||||
|
||||
@@ -82,11 +82,11 @@ https://docs.cherry-ai.com
|
||||
|
||||
# 🌈 Theme
|
||||
|
||||
- Theme Gallery: https://cherrycss.com
|
||||
- Aero Theme: https://github.com/hakadao/CherryStudio-Aero
|
||||
- PaperMaterial Theme: https://github.com/rainoffallingstar/CherryStudio-PaperMaterial
|
||||
- Claude dynamic-style: https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
|
||||
- Maple Neon Theme: https://github.com/BoningtonChen/CherryStudio_themes
|
||||
- Theme Gallery: <https://cherrycss.com>
|
||||
- Aero Theme: <https://github.com/hakadao/CherryStudio-Aero>
|
||||
- PaperMaterial Theme: <https://github.com/rainoffallingstar/CherryStudio-PaperMaterial>
|
||||
- Claude dynamic-style: <https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic>
|
||||
- Maple Neon Theme: <https://github.com/BoningtonChen/CherryStudio_themes>
|
||||
|
||||
Welcome PR for more themes
|
||||
|
||||
@@ -94,6 +94,10 @@ Welcome PR for more themes
|
||||
|
||||
Refer to the [development documentation](docs/dev.md)
|
||||
|
||||
Refer to the [Architecture overview documentation](https://deepwiki.com/CherryHQ/cherry-studio)
|
||||
|
||||
Refer to the [Branching Strategy](docs/branching-strategy.md) for contribution guidelines
|
||||
|
||||
# 🤝 Contributing
|
||||
|
||||
We welcome contributions to Cherry Studio! Here are some ways you can contribute:
|
||||
@@ -144,7 +148,7 @@ Thank you for your support and contributions!
|
||||
|
||||
# ✉️ Contact
|
||||
|
||||
yinsenho@cherry-ai.com
|
||||
<yinsenho@cherry-ai.com>
|
||||
|
||||
# ⭐️ Star History
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# 🌿 Branching Strategy
|
||||
|
||||
Cherry Studio follows a structured branching strategy to maintain code quality and streamline the development process:
|
||||
|
||||
## Main Branches
|
||||
|
||||
- `main`: Production-ready branch containing stable releases
|
||||
|
||||
- All code here is thoroughly tested and ready for production
|
||||
- Direct commits are not allowed - changes must come through pull requests
|
||||
- Each merge to main represents a new release
|
||||
|
||||
- `develop` (default): Primary development branch
|
||||
- Contains the latest delivered development changes for the next release
|
||||
- Relatively stable but may contain features in progress
|
||||
- This is the default branch for development
|
||||
|
||||
## Contributing Branches
|
||||
|
||||
When contributing to Cherry Studio, please follow these guidelines:
|
||||
|
||||
1. **For bug fixes:**
|
||||
|
||||
- Create a branch from `develop`
|
||||
- Name format: `fix/issue-number-brief-description`
|
||||
- Submit pull request back to `develop`
|
||||
|
||||
2. **For new features:**
|
||||
|
||||
- Create a branch from `develop`
|
||||
- Name format: `feature/issue-number-brief-description`
|
||||
- Submit pull request back to `develop`
|
||||
|
||||
3. **For documentation:**
|
||||
|
||||
- Create a branch from `develop`
|
||||
- Name format: `docs/brief-description`
|
||||
- Submit pull request back to `develop`
|
||||
|
||||
4. **For critical hotfixes:**
|
||||
- Create a branch from `main`
|
||||
- Name format: `hotfix/issue-number-brief-description`
|
||||
- Submit pull request to both `main` and `develop`
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Always create pull requests against the `develop` branch unless fixing a critical production issue
|
||||
- Ensure your branch is up to date with the latest `develop` changes before submitting
|
||||
- Include relevant issue numbers in your PR description
|
||||
- Make sure all tests pass and code meets our quality standards
|
||||
- Critical hotfixes may be submitted against `main` but must also be merged into `develop`
|
||||
@@ -96,9 +96,9 @@ afterSign: scripts/notarize.js
|
||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
新增对 grok-2-image 和 gpt-4o-image 图像支持
|
||||
支持 Windows 便携版使用 data 目录存储数据
|
||||
MCP 界面改版,新增描述信息显示
|
||||
Mermaid 渲染逻辑优化
|
||||
支持关闭公示渲染
|
||||
修复 OpenAI 类型渲染错误
|
||||
重构消息结构,支持不同类型消息按时间顺序显示
|
||||
智能体支持导入和导出
|
||||
快捷面板增加网络搜索引擎选择
|
||||
显示设置增加缩放控制按钮
|
||||
支持添加自定义小程序
|
||||
性能优化和错误修复
|
||||
|
||||
@@ -75,6 +75,14 @@ export default defineConfig({
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: []
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'src/renderer/index.html'),
|
||||
miniWindow: resolve(__dirname, 'src/renderer/miniWindow.html')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.2.10",
|
||||
"version": "1.3.0",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -77,7 +77,6 @@
|
||||
"adm-zip": "^0.5.16",
|
||||
"archiver": "^7.0.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"bufferutil": "^4.0.9",
|
||||
"canvas": "3.1.0",
|
||||
"color": "^5.0.0",
|
||||
"diff": "^7.0.0",
|
||||
@@ -96,7 +95,6 @@
|
||||
"markdown-it": "^14.1.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"officeparser": "^4.1.1",
|
||||
"opendal": "^0.47.11",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"pdf-to-img": "^4.4.0",
|
||||
"pdfjs-dist": "4.2.67",
|
||||
@@ -185,6 +183,7 @@
|
||||
"lru-cache": "^11.1.0",
|
||||
"lucide-react": "^0.487.0",
|
||||
"mime": "^4.0.4",
|
||||
"motion": "^12.10.5",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"openai": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
|
||||
"p-queue": "^8.1.0",
|
||||
|
||||
@@ -13,8 +13,7 @@ export enum IpcChannel {
|
||||
App_RestartTray = 'app:restart-tray',
|
||||
App_SetTheme = 'app:set-theme',
|
||||
App_SetAutoUpdate = 'app:set-auto-update',
|
||||
App_SetZoomFactor = 'app:set-zoom-factor',
|
||||
ZoomFactorUpdated = 'app:zoom-factor-updated',
|
||||
App_HandleZoomFactor = 'app:handle-zoom-factor',
|
||||
|
||||
App_IsBinaryExist = 'app:is-binary-exist',
|
||||
App_GetBinaryPath = 'app:get-binary-path',
|
||||
|
||||
@@ -7,7 +7,6 @@ exports.default = async function notarizing(context) {
|
||||
}
|
||||
|
||||
if (!process.env.APPLE_ID || !process.env.APPLE_APP_SPECIFIC_PASSWORD || !process.env.APPLE_TEAM_ID) {
|
||||
console.log('Skipping notarization')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* OCOOL_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts
|
||||
* Paratera_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts
|
||||
*/
|
||||
|
||||
// OCOOL API KEY
|
||||
const OCOOL_API_KEY = process.env.OCOOL_API_KEY
|
||||
const Paratera_API_KEY = process.env.Paratera_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' }
|
||||
{ name: 'France', code: 'fr-fr', model: 'Qwen3-235B-A22B' },
|
||||
{ name: 'Spanish', code: 'es-es', model: 'Qwen3-235B-A22B' },
|
||||
{ name: 'Portuguese', code: 'pt-pt', model: 'Qwen3-235B-A22B' },
|
||||
{ name: 'Greek', code: 'el-gr', model: 'Qwen3-235B-A22B' }
|
||||
]
|
||||
|
||||
const fs = require('fs')
|
||||
@@ -19,8 +19,8 @@ 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'
|
||||
apiKey: Paratera_API_KEY,
|
||||
baseURL: 'https://llmapi.paratera.com/v1'
|
||||
})
|
||||
|
||||
// 递归遍历翻译
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces'
|
||||
import { VoyageEmbeddings as _VoyageEmbeddings } from '@langchain/community/embeddings/voyage'
|
||||
import Logger from 'electron-log'
|
||||
|
||||
export default class VoyageEmbeddings extends BaseEmbeddings {
|
||||
private model: _VoyageEmbeddings
|
||||
@@ -11,7 +12,7 @@ export default class VoyageEmbeddings extends BaseEmbeddings {
|
||||
if (!this.configuration.outputDimension) {
|
||||
throw new Error('You need to pass in the optional dimensions parameter for this model')
|
||||
}
|
||||
console.log('VoyageEmbeddings', this.configuration)
|
||||
Logger.log('VoyageEmbeddings', this.configuration)
|
||||
this.model = new _VoyageEmbeddings(this.configuration)
|
||||
}
|
||||
override async getDimensions(): Promise<number> {
|
||||
|
||||
@@ -19,6 +19,8 @@ import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { setUserDataDir } from './utils/file'
|
||||
|
||||
Logger.initialize()
|
||||
|
||||
// Check for single instance lock
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
|
||||
+4
-8
@@ -3,6 +3,7 @@ import { arch } from 'node:os'
|
||||
|
||||
import { isMac, isWin } from '@main/constant'
|
||||
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
||||
import { handleZoomFactor } from '@main/utils/zoom'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { LocalFileSource, Shortcut, ThemeMode } from '@types'
|
||||
import { BrowserWindow, ipcMain, nativeTheme, session, shell } from 'electron'
|
||||
@@ -141,15 +142,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
notifyThemeChange()
|
||||
})
|
||||
|
||||
// zoom factor
|
||||
ipcMain.handle(IpcChannel.App_SetZoomFactor, (_, factor: number) => {
|
||||
configManager.setZoomFactor(factor)
|
||||
ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => {
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
windows.forEach((win) => {
|
||||
if (!win.isDestroyed()) {
|
||||
win.webContents.setZoomFactor(factor)
|
||||
}
|
||||
})
|
||||
handleZoomFactor(windows, delta, reset)
|
||||
return configManager.getZoomFactor()
|
||||
})
|
||||
|
||||
// clear cache
|
||||
|
||||
@@ -56,7 +56,6 @@ class DifyKnowledgeServer {
|
||||
private config: DifyKnowledgeServerConfig
|
||||
|
||||
constructor(difyKey: string, args: string[]) {
|
||||
console.log('DifyKnowledgeServer args', args)
|
||||
if (args.length === 0) {
|
||||
throw new Error('DifyKnowledgeServer requires at least one argument')
|
||||
}
|
||||
@@ -113,8 +112,6 @@ class DifyKnowledgeServer {
|
||||
const errorDetails = JSON.stringify(parsed.error.format(), null, 2)
|
||||
throw new Error(`无效的参数:\n${errorDetails}`)
|
||||
}
|
||||
|
||||
console.log('DifyKnowledgeServer search_knowledge parsed', parsed.data)
|
||||
return await this.performSearchKnowledge(
|
||||
parsed.data.id,
|
||||
parsed.data.query,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getConfigDir } from '@main/utils/file'
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
||||
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { Mutex } from 'async-mutex' // 引入 Mutex
|
||||
import Logger from 'electron-log'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
@@ -355,9 +356,9 @@ class MemoryServer {
|
||||
private async _initializeManager(memoryPath: string): Promise<void> {
|
||||
try {
|
||||
this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath)
|
||||
console.log('KnowledgeGraphManager initialized successfully.')
|
||||
Logger.log('KnowledgeGraphManager initialized successfully.')
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize KnowledgeGraphManager:', error)
|
||||
Logger.error('Failed to initialize KnowledgeGraphManager:', error)
|
||||
// Server might be unusable, consider how to handle this state
|
||||
// Maybe set a flag and return errors for all tool calls?
|
||||
this.knowledgeGraphManager = null // Ensure it's null if init fails
|
||||
|
||||
@@ -17,6 +17,10 @@ export default abstract class BaseReranker {
|
||||
* Get Rerank Request Url
|
||||
*/
|
||||
protected getRerankUrl() {
|
||||
if (this.base.rerankModelProvider === 'dashscope') {
|
||||
return 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank'
|
||||
}
|
||||
|
||||
let baseURL = this.base?.rerankBaseURL?.endsWith('/')
|
||||
? this.base.rerankBaseURL.slice(0, -1)
|
||||
: this.base.rerankBaseURL
|
||||
@@ -28,6 +32,56 @@ export default abstract class BaseReranker {
|
||||
return `${baseURL}/rerank`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rerank Request Body
|
||||
*/
|
||||
protected getRerankRequestBody(query: string, searchResults: ExtractChunkData[]) {
|
||||
const provider = this.base.rerankModelProvider
|
||||
const documents = searchResults.map((doc) => doc.pageContent)
|
||||
const topN = this.base.topN || 5
|
||||
|
||||
if (provider === 'voyageai') {
|
||||
return {
|
||||
model: this.base.rerankModel,
|
||||
query,
|
||||
documents,
|
||||
top_k: topN
|
||||
}
|
||||
} else if (provider === 'dashscope') {
|
||||
return {
|
||||
model: this.base.rerankModel,
|
||||
input: {
|
||||
query,
|
||||
documents
|
||||
},
|
||||
parameters: {
|
||||
top_n: topN
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
model: this.base.rerankModel,
|
||||
query,
|
||||
documents,
|
||||
top_n: topN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Rerank Result
|
||||
*/
|
||||
protected extractRerankResult(data: any) {
|
||||
const provider = this.base.rerankModelProvider
|
||||
if (provider === 'dashscope') {
|
||||
return data.output.results
|
||||
} else if (provider === 'voyageai') {
|
||||
return data.data
|
||||
} else {
|
||||
return data.results
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rerank Result
|
||||
* @param searchResults
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
|
||||
import axiosProxy from '@main/services/AxiosProxy'
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
|
||||
interface DashscopeRerankResultItem {
|
||||
document: {
|
||||
text: string
|
||||
}
|
||||
index: number
|
||||
relevance_score: number
|
||||
}
|
||||
|
||||
interface DashscopeRerankResponse {
|
||||
output: {
|
||||
results: DashscopeRerankResultItem[]
|
||||
}
|
||||
usage: {
|
||||
total_tokens: number
|
||||
}
|
||||
request_id: string
|
||||
}
|
||||
|
||||
export default class DashscopeReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const url = 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank'
|
||||
|
||||
const requestBody = {
|
||||
model: this.base.rerankModel,
|
||||
input: {
|
||||
query,
|
||||
documents: searchResults.map((doc) => doc.pageContent)
|
||||
},
|
||||
parameters: {
|
||||
return_documents: true, // Recommended to be true to get document details if needed, though scores are primary
|
||||
top_n: this.base.topN || 5 // Default to 5 if topN is not specified, as per API example
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axiosProxy.axios.post<DashscopeRerankResponse>(url, requestBody, {
|
||||
headers: this.defaultHeaders()
|
||||
})
|
||||
|
||||
const rerankResults = data.output.results
|
||||
return this.getRerankResult(searchResults, rerankResults)
|
||||
} catch (error: any) {
|
||||
const errorDetails = this.formatErrorMessage(url, error, requestBody)
|
||||
console.error('Dashscope Reranker API 错误:', errorDetails)
|
||||
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
|
||||
export default class DefaultReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
|
||||
async rerank(): Promise<ExtractChunkData[]> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
|
||||
export default class JinaReranker extends BaseReranker {
|
||||
export default class GeneralReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
@@ -12,21 +12,15 @@ export default class JinaReranker extends BaseReranker {
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const url = this.getRerankUrl()
|
||||
|
||||
const requestBody = {
|
||||
model: this.base.rerankModel,
|
||||
query,
|
||||
documents: searchResults.map((doc) => doc.pageContent),
|
||||
top_n: this.base.topN
|
||||
}
|
||||
const requestBody = this.getRerankRequestBody(query, searchResults)
|
||||
|
||||
try {
|
||||
const { data } = await AxiosProxy.axios.post(url, requestBody, { headers: this.defaultHeaders() })
|
||||
|
||||
const rerankResults = data.results
|
||||
const rerankResults = this.extractRerankResult(data)
|
||||
return this.getRerankResult(searchResults, rerankResults)
|
||||
} catch (error: any) {
|
||||
const errorDetails = this.formatErrorMessage(url, error, requestBody)
|
||||
console.error('Jina Reranker API Error:', errorDetails)
|
||||
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
import RerankerFactory from './RerankerFactory'
|
||||
import GeneralReranker from './GeneralReranker'
|
||||
|
||||
export default class Reranker {
|
||||
private sdk: BaseReranker
|
||||
private sdk: GeneralReranker
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
this.sdk = RerankerFactory.create(base)
|
||||
this.sdk = new GeneralReranker(base)
|
||||
}
|
||||
public async rerank(query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> {
|
||||
return this.sdk.rerank(query, searchResults)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
import DashscopeReranker from './DashscopeReranker'
|
||||
import DefaultReranker from './DefaultReranker'
|
||||
import JinaReranker from './JinaReranker'
|
||||
import SiliconFlowReranker from './SiliconFlowReranker'
|
||||
import VoyageReranker from './VoyageReranker'
|
||||
|
||||
export default class RerankerFactory {
|
||||
static create(base: KnowledgeBaseParams): BaseReranker {
|
||||
if (base.rerankModelProvider === 'silicon') {
|
||||
return new SiliconFlowReranker(base)
|
||||
} else if (base.rerankModelProvider === 'jina') {
|
||||
return new JinaReranker(base)
|
||||
} else if (base.rerankModelProvider === 'voyageai') {
|
||||
return new VoyageReranker(base)
|
||||
} else if (base.rerankModelProvider === 'dashscope') {
|
||||
return new DashscopeReranker(base)
|
||||
}
|
||||
return new DefaultReranker(base)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
|
||||
import axiosProxy from '@main/services/AxiosProxy'
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
|
||||
export default class SiliconFlowReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const url = this.getRerankUrl()
|
||||
|
||||
const requestBody = {
|
||||
model: this.base.rerankModel,
|
||||
query,
|
||||
documents: searchResults.map((doc) => doc.pageContent),
|
||||
top_n: this.base.topN,
|
||||
max_chunks_per_doc: this.base.chunkSize,
|
||||
overlap_tokens: this.base.chunkOverlap
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axiosProxy.axios.post(url, requestBody, { headers: this.defaultHeaders() })
|
||||
|
||||
const rerankResults = data.results
|
||||
return this.getRerankResult(searchResults, rerankResults)
|
||||
} catch (error: any) {
|
||||
const errorDetails = this.formatErrorMessage(url, error, requestBody)
|
||||
|
||||
console.error('SiliconFlow Reranker API 错误:', errorDetails)
|
||||
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
|
||||
import axiosProxy from '@main/services/AxiosProxy'
|
||||
import { KnowledgeBaseParams } from '@types'
|
||||
|
||||
import BaseReranker from './BaseReranker'
|
||||
|
||||
export default class VoyageReranker extends BaseReranker {
|
||||
constructor(base: KnowledgeBaseParams) {
|
||||
super(base)
|
||||
}
|
||||
|
||||
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
|
||||
const url = this.getRerankUrl()
|
||||
|
||||
const requestBody = {
|
||||
model: this.base.rerankModel,
|
||||
query,
|
||||
documents: searchResults.map((doc) => doc.pageContent),
|
||||
top_k: this.base.topN,
|
||||
return_documents: false,
|
||||
truncation: true
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axiosProxy.axios.post(url, requestBody, {
|
||||
headers: {
|
||||
...this.defaultHeaders()
|
||||
}
|
||||
})
|
||||
|
||||
const rerankResults = data.data
|
||||
return this.getRerankResult(searchResults, rerankResults)
|
||||
} catch (error: any) {
|
||||
const errorDetails = this.formatErrorMessage(url, error, requestBody)
|
||||
|
||||
console.error('Voyage Reranker API Error:', errorDetails)
|
||||
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import { app, safeStorage } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
@@ -227,10 +228,10 @@ class CopilotService {
|
||||
try {
|
||||
await fs.access(this.tokenFilePath)
|
||||
await fs.unlink(this.tokenFilePath)
|
||||
console.log('Successfully logged out from Copilot')
|
||||
Logger.log('Successfully logged out from Copilot')
|
||||
} catch (error) {
|
||||
// 文件不存在不是错误,只是记录一下
|
||||
console.log('Token file not found, nothing to delete')
|
||||
Logger.log('Token file not found, nothing to delete')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to logout:', error)
|
||||
|
||||
@@ -465,7 +465,7 @@ class KnowledgeService {
|
||||
{ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }
|
||||
): Promise<void> => {
|
||||
const ragApplication = await this.getRagApplication(base)
|
||||
console.log(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`)
|
||||
Logger.log(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`)
|
||||
for (const id of uniqueIds) {
|
||||
await ragApplication.deleteLoader(id)
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ class McpService {
|
||||
const pathSeparator = process.platform === 'win32' ? ';' : ':'
|
||||
const cherryBinPath = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
loginEnv.PATH = `${loginEnv.PATH}${pathSeparator}${cherryBinPath}`
|
||||
Logger.info('[MCP] Successfully fetched login shell environment variables:', loginEnv)
|
||||
Logger.info('[MCP] Successfully fetched login shell environment variables:')
|
||||
return loginEnv
|
||||
} catch (error) {
|
||||
Logger.error('[MCP] Failed to fetch login shell environment variables:', error)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { app } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
@@ -155,7 +156,7 @@ class ObsidianVaultService {
|
||||
return []
|
||||
}
|
||||
|
||||
console.log('获取Vault文件结构:', vault.name, vault.path)
|
||||
Logger.log('获取Vault文件结构:', vault.name, vault.path)
|
||||
return this.getVaultStructure(vault.path)
|
||||
} catch (error) {
|
||||
console.error('获取Vault文件结构时发生错误:', error)
|
||||
|
||||
@@ -25,7 +25,6 @@ export function handleProtocolUrl(url: string) {
|
||||
if (!url) return
|
||||
// Process the URL that was used to open the app
|
||||
// The url will be in the format: cherrystudio://data?param1=value1¶m2=value2
|
||||
console.log('Received URL:', url)
|
||||
|
||||
// Parse the URL and extract parameters
|
||||
const urlObj = new URL(url)
|
||||
|
||||
@@ -191,7 +191,7 @@ export const reduxService = new ReduxService()
|
||||
try {
|
||||
// 读取状态
|
||||
const settings = await reduxService.select('state.settings')
|
||||
console.log('settings', settings)
|
||||
Logger.log('settings', settings)
|
||||
|
||||
// 派发 action
|
||||
await reduxService.dispatch({
|
||||
@@ -201,7 +201,7 @@ export const reduxService = new ReduxService()
|
||||
|
||||
// 订阅状态变化
|
||||
const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => {
|
||||
console.log('API key changed:', newValue)
|
||||
Logger.log('API key changed:', newValue)
|
||||
})
|
||||
|
||||
// 批量执行 actions
|
||||
@@ -212,16 +212,16 @@ export const reduxService = new ReduxService()
|
||||
|
||||
// 同步方法虽然可能不是最新的数据,但响应更快
|
||||
const apiKey = reduxService.selectSync('state.settings.apiKey')
|
||||
console.log('apiKey', apiKey)
|
||||
Logger.log('apiKey', apiKey)
|
||||
|
||||
// 处理保证是最新的数据
|
||||
const apiKey1 = await reduxService.select('state.settings.apiKey')
|
||||
console.log('apiKey1', apiKey1)
|
||||
Logger.log('apiKey1', apiKey1)
|
||||
|
||||
// 取消订阅
|
||||
unsubscribe()
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
Logger.error('Error:', error)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import Logger from 'electron-log'
|
||||
import { Operator } from 'opendal'
|
||||
// import Logger from 'electron-log'
|
||||
// import { Operator } from 'opendal'
|
||||
|
||||
export default class RemoteStorage {
|
||||
public instance: Operator | undefined
|
||||
// export default class RemoteStorage {
|
||||
// public instance: Operator | undefined
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk"
|
||||
* @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options.
|
||||
*
|
||||
* For example, use minio as remote storage:
|
||||
*
|
||||
* ```typescript
|
||||
* const storage = new RemoteStorage('s3', {
|
||||
* endpoint: 'http://localhost:9000',
|
||||
* region: 'us-east-1',
|
||||
* bucket: 'testbucket',
|
||||
* access_key_id: 'user',
|
||||
* secret_access_key: 'password',
|
||||
* root: '/path/to/basepath',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(scheme: string, options?: Record<string, string> | undefined | null) {
|
||||
this.instance = new Operator(scheme, options)
|
||||
// /**
|
||||
// *
|
||||
// * @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk"
|
||||
// * @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options.
|
||||
// *
|
||||
// * For example, use minio as remote storage:
|
||||
// *
|
||||
// * ```typescript
|
||||
// * const storage = new RemoteStorage('s3', {
|
||||
// * endpoint: 'http://localhost:9000',
|
||||
// * region: 'us-east-1',
|
||||
// * bucket: 'testbucket',
|
||||
// * access_key_id: 'user',
|
||||
// * secret_access_key: 'password',
|
||||
// * root: '/path/to/basepath',
|
||||
// * })
|
||||
// * ```
|
||||
// */
|
||||
// constructor(scheme: string, options?: Record<string, string> | undefined | null) {
|
||||
// this.instance = new Operator(scheme, options)
|
||||
|
||||
this.putFileContents = this.putFileContents.bind(this)
|
||||
this.getFileContents = this.getFileContents.bind(this)
|
||||
}
|
||||
// this.putFileContents = this.putFileContents.bind(this)
|
||||
// this.getFileContents = this.getFileContents.bind(this)
|
||||
// }
|
||||
|
||||
public putFileContents = async (filename: string, data: string | Buffer) => {
|
||||
if (!this.instance) {
|
||||
return new Error('RemoteStorage client not initialized')
|
||||
}
|
||||
// public putFileContents = async (filename: string, data: string | Buffer) => {
|
||||
// if (!this.instance) {
|
||||
// return new Error('RemoteStorage client not initialized')
|
||||
// }
|
||||
|
||||
try {
|
||||
return await this.instance.write(filename, data)
|
||||
} catch (error) {
|
||||
Logger.error('[RemoteStorage] Error putting file contents:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// return await this.instance.write(filename, data)
|
||||
// } catch (error) {
|
||||
// Logger.error('[RemoteStorage] Error putting file contents:', error)
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
|
||||
public getFileContents = async (filename: string) => {
|
||||
if (!this.instance) {
|
||||
throw new Error('RemoteStorage client not initialized')
|
||||
}
|
||||
// public getFileContents = async (filename: string) => {
|
||||
// if (!this.instance) {
|
||||
// throw new Error('RemoteStorage client not initialized')
|
||||
// }
|
||||
|
||||
try {
|
||||
return await this.instance.read(filename)
|
||||
} catch (error) {
|
||||
Logger.error('[RemoteStorage] Error getting file contents:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// return await this.instance.read(filename)
|
||||
// } catch (error) {
|
||||
// Logger.error('[RemoteStorage] Error getting file contents:', error)
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ZOOM_LEVELS } from '@shared/config/constant'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { handleZoomFactor } from '@main/utils/zoom'
|
||||
import { Shortcut } from '@types'
|
||||
import { BrowserWindow, globalShortcut } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
@@ -16,14 +15,11 @@ const windowOnHandlers = new Map<BrowserWindow, { onFocusHandler: () => void; on
|
||||
function getShortcutHandler(shortcut: Shortcut) {
|
||||
switch (shortcut.key) {
|
||||
case 'zoom_in':
|
||||
return (window: BrowserWindow) => handleZoom(1)(window)
|
||||
return (window: BrowserWindow) => handleZoomFactor([window], 0.1)
|
||||
case 'zoom_out':
|
||||
return (window: BrowserWindow) => handleZoom(-1)(window)
|
||||
return (window: BrowserWindow) => handleZoomFactor([window], -0.1)
|
||||
case 'zoom_reset':
|
||||
return (window: BrowserWindow) => {
|
||||
window.webContents.setZoomFactor(1)
|
||||
configManager.setZoomFactor(1)
|
||||
}
|
||||
return (window: BrowserWindow) => handleZoomFactor([window], 0, true)
|
||||
case 'show_app':
|
||||
return () => {
|
||||
windowService.toggleMainWindow()
|
||||
@@ -41,46 +37,6 @@ function formatShortcutKey(shortcut: string[]): string {
|
||||
return shortcut.join('+')
|
||||
}
|
||||
|
||||
function handleZoom(delta: number) {
|
||||
return (window: BrowserWindow) => {
|
||||
const currentZoom = configManager.getZoomFactor()
|
||||
let currentIndex = ZOOM_LEVELS.indexOf(currentZoom)
|
||||
|
||||
// 如果当前缩放比例不在预设列表中,找到最接近的
|
||||
if (currentIndex === -1) {
|
||||
let closestIndex = 0
|
||||
let minDiff = Math.abs(ZOOM_LEVELS[0] - currentZoom)
|
||||
for (let i = 1; i < ZOOM_LEVELS.length; i++) {
|
||||
const diff = Math.abs(ZOOM_LEVELS[i] - currentZoom)
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff
|
||||
closestIndex = i
|
||||
}
|
||||
}
|
||||
currentIndex = closestIndex
|
||||
}
|
||||
|
||||
let nextIndex = currentIndex + delta
|
||||
|
||||
// 边界检查
|
||||
if (nextIndex < 0) {
|
||||
nextIndex = 0 // 已经是最小值
|
||||
} else if (nextIndex >= ZOOM_LEVELS.length) {
|
||||
nextIndex = ZOOM_LEVELS.length - 1 // 已经是最大值
|
||||
}
|
||||
|
||||
const newZoom = ZOOM_LEVELS[nextIndex]
|
||||
|
||||
if (newZoom !== currentZoom) {
|
||||
// 只有在实际改变时才更新
|
||||
configManager.setZoomFactor(newZoom)
|
||||
// 通知所有渲染进程更新 zoomFactor
|
||||
window.webContents.setZoomFactor(newZoom)
|
||||
window.webContents.send(IpcChannel.ZoomFactorUpdated, newZoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat = (
|
||||
shortcut: string | string[]
|
||||
): string => {
|
||||
|
||||
@@ -468,9 +468,9 @@ export class WindowService {
|
||||
})
|
||||
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
this.miniWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/src/windows/mini/index.html')
|
||||
this.miniWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/miniWindow.html')
|
||||
} else {
|
||||
this.miniWindow.loadFile(join(__dirname, '../renderer/src/windows/mini/index.html'))
|
||||
this.miniWindow.loadFile(join(__dirname, '../renderer/miniWindow.html'))
|
||||
}
|
||||
|
||||
return this.miniWindow
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
|
||||
import { configManager } from '../services/ConfigManager'
|
||||
|
||||
export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) {
|
||||
if (reset) {
|
||||
wins.forEach((win) => {
|
||||
win.webContents.setZoomFactor(1)
|
||||
})
|
||||
configManager.setZoomFactor(1)
|
||||
return
|
||||
}
|
||||
|
||||
if (delta === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentZoom = configManager.getZoomFactor()
|
||||
const newZoom = Number((currentZoom + delta).toFixed(1))
|
||||
if (newZoom >= 0.5 && newZoom <= 2.0) {
|
||||
wins.forEach((win) => {
|
||||
win.webContents.setZoomFactor(newZoom)
|
||||
})
|
||||
configManager.setZoomFactor(newZoom)
|
||||
}
|
||||
}
|
||||
+2
-12
@@ -19,7 +19,8 @@ const api = {
|
||||
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive),
|
||||
restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray),
|
||||
setTheme: (theme: 'light' | 'dark' | 'auto') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
|
||||
setZoomFactor: (factor: number) => ipcRenderer.invoke(IpcChannel.App_SetZoomFactor, factor),
|
||||
handleZoomFactor: (delta: number, reset: boolean = false) =>
|
||||
ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset),
|
||||
setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive),
|
||||
openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url),
|
||||
clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache),
|
||||
@@ -198,17 +199,6 @@ const api = {
|
||||
subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe),
|
||||
unsubscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Unsubscribe),
|
||||
onUpdate: (action: any) => ipcRenderer.invoke(IpcChannel.StoreSync_OnUpdate, action)
|
||||
},
|
||||
// 新增:监听主进程的 zoom factor 更新
|
||||
onZoomFactorUpdate: (callback: (factor: number) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, factor: number) => {
|
||||
callback(factor)
|
||||
}
|
||||
ipcRenderer.on(IpcChannel.ZoomFactorUpdated, listener)
|
||||
// 返回一个移除监听器的函数
|
||||
return () => {
|
||||
ipcRenderer.removeListener(IpcChannel.ZoomFactorUpdated, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { vi } from 'vitest'
|
||||
|
||||
vi.mock('electron-log/renderer', () => {
|
||||
return {
|
||||
default: {
|
||||
info: console.log,
|
||||
error: console.error,
|
||||
warn: console.warn,
|
||||
debug: console.debug,
|
||||
verbose: console.log,
|
||||
silly: console.log,
|
||||
log: console.log,
|
||||
transports: {
|
||||
console: {
|
||||
level: 'info'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./entryPoint.tsx"></script>
|
||||
<script type="module" src="/src/windows/mini/entryPoint.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
@@ -1,6 +1,6 @@
|
||||
import { CloseOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from 'antd'
|
||||
import { FC, memo, useEffect, useMemo, useState } from 'react'
|
||||
import { FC, memo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface CustomTagProps {
|
||||
@@ -14,29 +14,13 @@ interface CustomTagProps {
|
||||
}
|
||||
|
||||
const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, tooltip, closable = false, onClose }) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setShowTooltip(true), 300)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
const tagContent = useMemo(
|
||||
() => (
|
||||
return (
|
||||
<Tooltip title={tooltip} placement="top">
|
||||
<Tag $color={color} $size={size} $closable={closable}>
|
||||
{icon && icon} {children}
|
||||
{closable && <CloseIcon $size={size} $color={color} onClick={onClose} />}
|
||||
</Tag>
|
||||
),
|
||||
[children, color, closable, icon, onClose, size]
|
||||
)
|
||||
|
||||
return tooltip && showTooltip ? (
|
||||
<Tooltip title={tooltip} placement="top">
|
||||
{tagContent}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagContent
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Logger from '@renderer/config/logger'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@@ -120,7 +121,7 @@ const FallbackFavicon: React.FC<FallbackFaviconProps> = ({ hostname, alt }) => {
|
||||
setFaviconState({ status: 'loaded', src: url })
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('All favicon requests failed:', error)
|
||||
Logger.log('All favicon requests failed:', error)
|
||||
setFaviconState({ status: 'loaded', src: faviconUrls[0] })
|
||||
})
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import { Avatar, Drawer, Tooltip } from 'antd'
|
||||
import { WebviewTag } from 'electron'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BeatLoader from 'react-spinners/BeatLoader'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SvgSpinners180Ring from '../Icons/SvgSpinners180Ring'
|
||||
import WebviewContainer from './WebviewContainer'
|
||||
|
||||
interface AppExtraInfo {
|
||||
@@ -375,7 +375,7 @@ const MinappPopupContainer: React.FC = () => {
|
||||
size={80}
|
||||
style={{ border: '1px solid var(--color-border)', marginTop: -150 }}
|
||||
/>
|
||||
<SvgSpinners180Ring color="var(--color-text-2)" style={{ marginTop: 15 }} />
|
||||
<BeatLoader color="var(--color-text-2)" size={10} style={{ marginTop: 15 }} />
|
||||
</EmptyView>
|
||||
)}
|
||||
{WebviewContainerGroup}
|
||||
|
||||
@@ -312,6 +312,7 @@ const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
||||
closable
|
||||
maskClosable
|
||||
centered
|
||||
transitionName="animation-move-down"
|
||||
okButtonProps={{
|
||||
type: 'primary',
|
||||
disabled: vaults.length === 0 || loading || !!error
|
||||
|
||||
@@ -150,7 +150,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="ant-move-up"
|
||||
transitionName="animation-move-down"
|
||||
styles={{
|
||||
content: {
|
||||
borderRadius: 20,
|
||||
|
||||
@@ -70,6 +70,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
cancelButtonProps={{ disabled: isDisabled }}
|
||||
okText={t('backup.confirm.button')}
|
||||
maskClosable={false}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
{!progressData && <div>{t('backup.content')}</div>}
|
||||
{progressData && (
|
||||
|
||||
@@ -63,6 +63,7 @@ const PromptPopupContainer: React.FC<Props> = ({
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
afterOpenChange={handleAfterOpenChange}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Box mb={8}>{message}</Box>
|
||||
<Input.TextArea
|
||||
|
||||
@@ -70,6 +70,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
okButtonProps={{ disabled: isDisabled }}
|
||||
cancelButtonProps={{ disabled: isDisabled }}
|
||||
maskClosable={false}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
{!progressData && <div>{t('restore.content')}</div>}
|
||||
{progressData && (
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo, useReducer } from 'react'
|
||||
|
||||
import { initialScrollState, scrollReducer } from './reducer'
|
||||
import { FlatListItem, ScrollTrigger } from './types'
|
||||
|
||||
/**
|
||||
* 管理滚动和焦点状态的 hook
|
||||
*/
|
||||
export function useScrollState() {
|
||||
const [state, dispatch] = useReducer(scrollReducer, initialScrollState)
|
||||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
setFocusedItemKey: (key: string) => dispatch({ type: 'SET_FOCUSED_ITEM_KEY', payload: key }),
|
||||
setScrollTrigger: (trigger: ScrollTrigger) => dispatch({ type: 'SET_SCROLL_TRIGGER', payload: trigger }),
|
||||
setLastScrollOffset: (offset: number) => dispatch({ type: 'SET_LAST_SCROLL_OFFSET', payload: offset }),
|
||||
setStickyGroup: (group: FlatListItem | null) => dispatch({ type: 'SET_STICKY_GROUP', payload: group }),
|
||||
setIsMouseOver: (isMouseOver: boolean) => dispatch({ type: 'SET_IS_MOUSE_OVER', payload: isMouseOver }),
|
||||
focusNextItem: (modelItems: FlatListItem[], step: number) =>
|
||||
dispatch({ type: 'FOCUS_NEXT_ITEM', payload: { modelItems, step } }),
|
||||
focusPage: (modelItems: FlatListItem[], currentIndex: number, step: number) =>
|
||||
dispatch({ type: 'FOCUS_PAGE', payload: { modelItems, currentIndex, step } }),
|
||||
searchChanged: (searchText: string) => dispatch({ type: 'SEARCH_CHANGED', payload: { searchText } }),
|
||||
updateOnListChange: (modelItems: FlatListItem[]) =>
|
||||
dispatch({ type: 'UPDATE_ON_LIST_CHANGE', payload: { modelItems } }),
|
||||
initScroll: () => dispatch({ type: 'INIT_SCROLL' })
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
// 状态
|
||||
focusedItemKey: state.focusedItemKey,
|
||||
scrollTrigger: state.scrollTrigger,
|
||||
lastScrollOffset: state.lastScrollOffset,
|
||||
stickyGroup: state.stickyGroup,
|
||||
isMouseOver: state.isMouseOver,
|
||||
// 操作
|
||||
...actions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { SelectModelPopup } from './popup'
|
||||
|
||||
export default SelectModelPopup
|
||||
+118
-168
@@ -1,7 +1,9 @@
|
||||
import { PushpinOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { getModelLogo, isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import db from '@renderer/databases'
|
||||
import { usePinnedModels } from '@renderer/hooks/usePinnedModels'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { Model } from '@renderer/types'
|
||||
@@ -15,101 +17,61 @@ import { useTranslation } from 'react-i18next'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { HStack } from '../Layout'
|
||||
import ModelTagsWithLabel from '../ModelTagsWithLabel'
|
||||
import { useScrollState } from './hook'
|
||||
import { FlatListItem } from './types'
|
||||
|
||||
const PAGE_SIZE = 9
|
||||
const PAGE_SIZE = 10
|
||||
const ITEM_HEIGHT = 36
|
||||
|
||||
// 列表项类型,组名也作为列表项
|
||||
type ListItemType = 'group' | 'model'
|
||||
|
||||
// 滚动触发来源类型
|
||||
type ScrollTrigger = 'initial' | 'search' | 'keyboard' | 'none'
|
||||
|
||||
// 扁平化列表项接口
|
||||
interface FlatListItem {
|
||||
key: string
|
||||
type: ListItemType
|
||||
icon?: React.ReactNode
|
||||
name: React.ReactNode
|
||||
tags?: React.ReactNode
|
||||
model?: Model
|
||||
isPinned?: boolean
|
||||
isSelected?: boolean
|
||||
}
|
||||
|
||||
interface Props {
|
||||
interface PopupParams {
|
||||
model?: Model
|
||||
}
|
||||
|
||||
interface PopupContainerProps extends Props {
|
||||
interface Props extends PopupParams {
|
||||
resolve: (value: Model | undefined) => void
|
||||
}
|
||||
|
||||
const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
const PopupContainer: React.FC<Props> = ({ model, resolve }) => {
|
||||
const { t } = useTranslation()
|
||||
const { providers } = useProviders()
|
||||
const { pinnedModels, togglePinnedModel, loading: loadingPinnedModels } = usePinnedModels()
|
||||
const [open, setOpen] = useState(true)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
const listRef = useRef<FixedSizeList>(null)
|
||||
const [_searchText, setSearchText] = useState('')
|
||||
const searchText = useDeferredValue(_searchText)
|
||||
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||
const [pinnedModels, setPinnedModels] = useState<string[]>([])
|
||||
const [_focusedItemKey, setFocusedItemKey] = useState<string>('')
|
||||
const focusedItemKey = useDeferredValue(_focusedItemKey)
|
||||
const [_stickyGroup, setStickyGroup] = useState<FlatListItem | null>(null)
|
||||
const stickyGroup = useDeferredValue(_stickyGroup)
|
||||
const firstGroupRef = useRef<FlatListItem | null>(null)
|
||||
const scrollTriggerRef = useRef<ScrollTrigger>('initial')
|
||||
const lastScrollOffsetRef = useRef(0)
|
||||
|
||||
// 当前选中的模型ID
|
||||
const currentModelId = model ? getModelUniqId(model) : ''
|
||||
|
||||
// 加载置顶模型列表
|
||||
useEffect(() => {
|
||||
const loadPinnedModels = async () => {
|
||||
const setting = await db.settings.get('pinned:models')
|
||||
const savedPinnedModels = setting?.value || []
|
||||
// 管理滚动和焦点状态
|
||||
const {
|
||||
focusedItemKey,
|
||||
scrollTrigger,
|
||||
lastScrollOffset,
|
||||
stickyGroup: _stickyGroup,
|
||||
isMouseOver,
|
||||
setFocusedItemKey,
|
||||
setScrollTrigger,
|
||||
setLastScrollOffset,
|
||||
setStickyGroup,
|
||||
setIsMouseOver,
|
||||
focusNextItem,
|
||||
focusPage,
|
||||
searchChanged,
|
||||
updateOnListChange,
|
||||
initScroll
|
||||
} = useScrollState()
|
||||
|
||||
// Filter out invalid pinned models
|
||||
const allModelIds = providers.flatMap((p) => p.models || []).map((m) => getModelUniqId(m))
|
||||
const validPinnedModels = savedPinnedModels.filter((id) => allModelIds.includes(id))
|
||||
|
||||
// Update storage if there were invalid models
|
||||
if (validPinnedModels.length !== savedPinnedModels.length) {
|
||||
await db.settings.put({ id: 'pinned:models', value: validPinnedModels })
|
||||
}
|
||||
|
||||
setPinnedModels(sortBy(validPinnedModels))
|
||||
}
|
||||
|
||||
try {
|
||||
loadPinnedModels()
|
||||
} catch (error) {
|
||||
console.error('Failed to load pinned models', error)
|
||||
setPinnedModels([])
|
||||
}
|
||||
}, [providers])
|
||||
const stickyGroup = useDeferredValue(_stickyGroup)
|
||||
const firstGroupRef = useRef<FlatListItem | null>(null)
|
||||
|
||||
const togglePin = useCallback(
|
||||
async (modelId: string) => {
|
||||
const newPinnedModels = pinnedModels.includes(modelId)
|
||||
? pinnedModels.filter((id) => id !== modelId)
|
||||
: [...pinnedModels, modelId]
|
||||
|
||||
try {
|
||||
await db.settings.put({ id: 'pinned:models', value: newPinnedModels })
|
||||
setPinnedModels(sortBy(newPinnedModels))
|
||||
// Pin操作不触发滚动
|
||||
scrollTriggerRef.current = 'none'
|
||||
} catch (error) {
|
||||
console.error('Failed to update pinned models', error)
|
||||
}
|
||||
await togglePinnedModel(modelId)
|
||||
setScrollTrigger('none') // pin操作不触发滚动
|
||||
},
|
||||
[pinnedModels]
|
||||
[togglePinnedModel, setScrollTrigger]
|
||||
)
|
||||
|
||||
// 根据输入的文本筛选模型
|
||||
@@ -222,6 +184,16 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
return items
|
||||
}, [providers, getFilteredModels, pinnedModels, searchText, t, createModelItem])
|
||||
|
||||
// 获取可选择的模型项(过滤掉分组标题)
|
||||
const modelItems = useMemo(() => {
|
||||
return listItems.filter((item) => item.type === 'model')
|
||||
}, [listItems])
|
||||
|
||||
// 当搜索文本变化时更新滚动触发器
|
||||
useEffect(() => {
|
||||
searchChanged(searchText)
|
||||
}, [searchText, searchChanged])
|
||||
|
||||
// 基于滚动位置更新sticky分组标题
|
||||
const updateStickyGroup = useCallback(
|
||||
(scrollOffset?: number) => {
|
||||
@@ -231,7 +203,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
}
|
||||
|
||||
// 基于滚动位置计算当前可见的第一个项的索引
|
||||
const estimatedIndex = Math.floor((scrollOffset ?? lastScrollOffsetRef.current) / ITEM_HEIGHT)
|
||||
const estimatedIndex = Math.floor((scrollOffset ?? lastScrollOffset) / ITEM_HEIGHT)
|
||||
|
||||
// 从该索引向前查找最近的分组标题
|
||||
for (let i = estimatedIndex - 1; i >= 0; i--) {
|
||||
@@ -242,9 +214,9 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
}
|
||||
|
||||
// 找不到则使用第一个分组标题
|
||||
setStickyGroup(firstGroupRef.current ?? null)
|
||||
setStickyGroup(firstGroupRef.current)
|
||||
},
|
||||
[listItems]
|
||||
[listItems, lastScrollOffset, setStickyGroup]
|
||||
)
|
||||
|
||||
// 在listItems变化时更新sticky group
|
||||
@@ -255,72 +227,47 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
// 处理列表滚动事件,更新lastScrollOffset并更新sticky分组
|
||||
const handleScroll = useCallback(
|
||||
({ scrollOffset }) => {
|
||||
lastScrollOffsetRef.current = scrollOffset
|
||||
setLastScrollOffset(scrollOffset)
|
||||
updateStickyGroup(scrollOffset)
|
||||
},
|
||||
[updateStickyGroup]
|
||||
[updateStickyGroup, setLastScrollOffset]
|
||||
)
|
||||
|
||||
// 获取可选择的模型项(过滤掉分组标题)
|
||||
const modelItems = useMemo(() => {
|
||||
return listItems.filter((item) => item.type === 'model')
|
||||
}, [listItems])
|
||||
|
||||
// 搜索文本变化时设置滚动来源
|
||||
// 在列表项更新时,更新焦点项
|
||||
useEffect(() => {
|
||||
if (searchText.trim() !== '') {
|
||||
scrollTriggerRef.current = 'search'
|
||||
setFocusedItemKey('')
|
||||
}
|
||||
}, [searchText])
|
||||
|
||||
// 设置初始聚焦项以触发滚动
|
||||
useEffect(() => {
|
||||
if (scrollTriggerRef.current === 'initial' || scrollTriggerRef.current === 'search') {
|
||||
const selectedItem = modelItems.find((item) => item.isSelected)
|
||||
if (selectedItem) {
|
||||
setFocusedItemKey(selectedItem.key)
|
||||
} else if (scrollTriggerRef.current === 'initial' && modelItems.length > 0) {
|
||||
setFocusedItemKey(modelItems[0].key)
|
||||
}
|
||||
// 其余情况不设置focusedItemKey
|
||||
}
|
||||
}, [modelItems])
|
||||
updateOnListChange(modelItems)
|
||||
}, [modelItems, updateOnListChange])
|
||||
|
||||
// 滚动到聚焦项
|
||||
useEffect(() => {
|
||||
if (scrollTriggerRef.current === 'none' || !focusedItemKey) return
|
||||
if (scrollTrigger === 'none' || !focusedItemKey) return
|
||||
|
||||
const index = listItems.findIndex((item) => item.key === focusedItemKey)
|
||||
if (index < 0) return
|
||||
|
||||
// 根据触发源决定滚动对齐方式
|
||||
const alignment = scrollTriggerRef.current === 'keyboard' ? 'auto' : 'center'
|
||||
const alignment = scrollTrigger === 'keyboard' ? 'auto' : 'center'
|
||||
listRef.current?.scrollToItem(index, alignment)
|
||||
|
||||
// 滚动后重置触发器
|
||||
scrollTriggerRef.current = 'none'
|
||||
}, [focusedItemKey, listItems])
|
||||
setScrollTrigger('none')
|
||||
}, [focusedItemKey, scrollTrigger, listItems, setScrollTrigger])
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(item: FlatListItem) => {
|
||||
if (item.type === 'model') {
|
||||
scrollTriggerRef.current = 'none'
|
||||
setScrollTrigger('initial')
|
||||
resolve(item.model)
|
||||
setOpen(false)
|
||||
}
|
||||
},
|
||||
[resolve]
|
||||
[resolve, setScrollTrigger]
|
||||
)
|
||||
|
||||
// 处理键盘导航
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!open) return
|
||||
|
||||
if (modelItems.length === 0) {
|
||||
return
|
||||
}
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (!open || modelItems.length === 0 || e.isComposing) return
|
||||
|
||||
// 键盘操作时禁用鼠标 hover
|
||||
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Enter', 'Escape'].includes(e.key)) {
|
||||
@@ -329,43 +276,21 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
setIsMouseOver(false)
|
||||
}
|
||||
|
||||
const getCurrentIndex = (currentKey: string) => {
|
||||
const currentIndex = modelItems.findIndex((item) => item.key === currentKey)
|
||||
return currentIndex < 0 ? 0 : currentIndex
|
||||
}
|
||||
const currentIndex = modelItems.findIndex((item) => item.key === focusedItemKey)
|
||||
const normalizedIndex = currentIndex < 0 ? 0 : currentIndex
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
scrollTriggerRef.current = 'keyboard'
|
||||
setFocusedItemKey((prev) => {
|
||||
const currentIndex = getCurrentIndex(prev)
|
||||
const nextIndex = (currentIndex - 1 + modelItems.length) % modelItems.length
|
||||
return modelItems[nextIndex].key
|
||||
})
|
||||
focusNextItem(modelItems, -1)
|
||||
break
|
||||
case 'ArrowDown':
|
||||
scrollTriggerRef.current = 'keyboard'
|
||||
setFocusedItemKey((prev) => {
|
||||
const currentIndex = getCurrentIndex(prev)
|
||||
const nextIndex = (currentIndex + 1) % modelItems.length
|
||||
return modelItems[nextIndex].key
|
||||
})
|
||||
focusNextItem(modelItems, 1)
|
||||
break
|
||||
case 'PageUp':
|
||||
scrollTriggerRef.current = 'keyboard'
|
||||
setFocusedItemKey((prev) => {
|
||||
const currentIndex = getCurrentIndex(prev)
|
||||
const nextIndex = Math.max(currentIndex - PAGE_SIZE, 0)
|
||||
return modelItems[nextIndex].key
|
||||
})
|
||||
focusPage(modelItems, normalizedIndex, -PAGE_SIZE)
|
||||
break
|
||||
case 'PageDown':
|
||||
scrollTriggerRef.current = 'keyboard'
|
||||
setFocusedItemKey((prev) => {
|
||||
const currentIndex = getCurrentIndex(prev)
|
||||
const nextIndex = Math.min(currentIndex + PAGE_SIZE, modelItems.length - 1)
|
||||
return modelItems[nextIndex].key
|
||||
})
|
||||
focusPage(modelItems, normalizedIndex, PAGE_SIZE)
|
||||
break
|
||||
case 'Enter':
|
||||
if (focusedItemKey) {
|
||||
@@ -377,34 +302,47 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
break
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
scrollTriggerRef.current = 'none'
|
||||
setScrollTrigger('none')
|
||||
setOpen(false)
|
||||
resolve(undefined)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [focusedItemKey, modelItems, handleItemClick, open, resolve])
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
scrollTriggerRef.current = 'none'
|
||||
setOpen(false)
|
||||
}, [])
|
||||
|
||||
const onClose = useCallback(async () => {
|
||||
scrollTriggerRef.current = 'none'
|
||||
resolve(undefined)
|
||||
SelectModelPopup.hide()
|
||||
}, [resolve])
|
||||
},
|
||||
[
|
||||
focusedItemKey,
|
||||
modelItems,
|
||||
handleItemClick,
|
||||
open,
|
||||
resolve,
|
||||
setIsMouseOver,
|
||||
focusNextItem,
|
||||
focusPage,
|
||||
setScrollTrigger
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [handleKeyDown])
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
setScrollTrigger('initial')
|
||||
setOpen(false)
|
||||
}, [setScrollTrigger])
|
||||
|
||||
const onClose = useCallback(async () => {
|
||||
setScrollTrigger('initial')
|
||||
resolve(undefined)
|
||||
SelectModelPopup.hide()
|
||||
}, [resolve, setScrollTrigger])
|
||||
|
||||
// 初始化焦点和滚动位置
|
||||
useEffect(() => {
|
||||
if (!open || loadingPinnedModels) return
|
||||
setTimeout(() => inputRef.current?.focus(), 0)
|
||||
scrollTriggerRef.current = 'initial'
|
||||
lastScrollOffsetRef.current = 0
|
||||
}, [open])
|
||||
initScroll()
|
||||
}, [open, initScroll, loadingPinnedModels])
|
||||
|
||||
const RowData = useMemo(
|
||||
(): VirtualizedRowData => ({
|
||||
@@ -415,7 +353,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
handleItemClick,
|
||||
togglePin
|
||||
}),
|
||||
[stickyGroup, focusedItemKey, handleItemClick, listItems, togglePin]
|
||||
[stickyGroup, focusedItemKey, handleItemClick, listItems, togglePin, setFocusedItemKey]
|
||||
)
|
||||
|
||||
const listHeight = useMemo(() => {
|
||||
@@ -470,7 +408,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
||||
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
|
||||
|
||||
{listItems.length > 0 ? (
|
||||
<ListContainer onMouseMove={() => setIsMouseOver(true)}>
|
||||
<ListContainer onMouseMove={() => !isMouseOver && setIsMouseOver(true)}>
|
||||
{/* Sticky Group Banner,它会替换第一个分组名称 */}
|
||||
<StickyGroupBanner>{stickyGroup?.name}</StickyGroupBanner>
|
||||
<FixedSizeList
|
||||
@@ -685,14 +623,26 @@ const PinIconWrapper = styled.div.attrs({ className: 'pin-icon' })<{ $isPinned?:
|
||||
}
|
||||
`
|
||||
|
||||
export default class SelectModelPopup {
|
||||
const TopViewKey = 'SelectModelPopup'
|
||||
|
||||
export class SelectModelPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('SelectModelPopup')
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
|
||||
static show(params: Props) {
|
||||
static show(params: PopupParams) {
|
||||
return new Promise<Model | undefined>((resolve) => {
|
||||
TopView.show(<PopupContainer {...params} resolve={resolve} />, 'SelectModelPopup')
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...params}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
TopViewKey
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { ScrollAction, ScrollState } from './types'
|
||||
|
||||
/**
|
||||
* 初始状态
|
||||
*/
|
||||
export const initialScrollState: ScrollState = {
|
||||
focusedItemKey: '',
|
||||
scrollTrigger: 'initial',
|
||||
lastScrollOffset: 0,
|
||||
stickyGroup: null,
|
||||
isMouseOver: false
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动状态的 reducer,用于避免复杂依赖可能带来的状态更新问题
|
||||
* @param state 当前状态
|
||||
* @param action 动作
|
||||
* @returns 新的状态
|
||||
*/
|
||||
export const scrollReducer = (state: ScrollState, action: ScrollAction): ScrollState => {
|
||||
switch (action.type) {
|
||||
case 'SET_FOCUSED_ITEM_KEY':
|
||||
return { ...state, focusedItemKey: action.payload }
|
||||
|
||||
case 'SET_SCROLL_TRIGGER':
|
||||
return { ...state, scrollTrigger: action.payload }
|
||||
|
||||
case 'SET_LAST_SCROLL_OFFSET':
|
||||
return { ...state, lastScrollOffset: action.payload }
|
||||
|
||||
case 'SET_STICKY_GROUP':
|
||||
return { ...state, stickyGroup: action.payload }
|
||||
|
||||
case 'SET_IS_MOUSE_OVER':
|
||||
return { ...state, isMouseOver: action.payload }
|
||||
|
||||
case 'FOCUS_NEXT_ITEM': {
|
||||
const { modelItems, step } = action.payload
|
||||
|
||||
if (modelItems.length === 0) {
|
||||
return {
|
||||
...state,
|
||||
focusedItemKey: '',
|
||||
scrollTrigger: 'keyboard'
|
||||
}
|
||||
}
|
||||
|
||||
const currentIndex = modelItems.findIndex((item) => item.key === state.focusedItemKey)
|
||||
const nextIndex = (currentIndex < 0 ? 0 : currentIndex + step + modelItems.length) % modelItems.length
|
||||
|
||||
return {
|
||||
...state,
|
||||
focusedItemKey: modelItems[nextIndex].key,
|
||||
scrollTrigger: 'keyboard'
|
||||
}
|
||||
}
|
||||
|
||||
case 'FOCUS_PAGE': {
|
||||
const { modelItems, currentIndex, step } = action.payload
|
||||
const nextIndex = Math.max(0, Math.min(currentIndex + step, modelItems.length - 1))
|
||||
|
||||
return {
|
||||
...state,
|
||||
focusedItemKey: modelItems.length > 0 ? modelItems[nextIndex].key : '',
|
||||
scrollTrigger: 'keyboard'
|
||||
}
|
||||
}
|
||||
|
||||
case 'SEARCH_CHANGED':
|
||||
return {
|
||||
...state,
|
||||
scrollTrigger: action.payload.searchText ? 'search' : 'initial'
|
||||
}
|
||||
|
||||
case 'UPDATE_ON_LIST_CHANGE': {
|
||||
const { modelItems } = action.payload
|
||||
|
||||
// 在列表变化时尝试聚焦一个模型:
|
||||
// - 如果是 initial 状态,先尝试聚焦当前选中的模型
|
||||
// - 如果是 search 状态,尝试聚焦第一个模型
|
||||
let newFocusedKey = ''
|
||||
if (state.scrollTrigger === 'initial' || state.scrollTrigger === 'search') {
|
||||
const selectedItem = modelItems.find((item) => item.isSelected)
|
||||
if (selectedItem && state.scrollTrigger === 'initial') {
|
||||
newFocusedKey = selectedItem.key
|
||||
} else if (modelItems.length > 0) {
|
||||
newFocusedKey = modelItems[0].key
|
||||
}
|
||||
} else {
|
||||
newFocusedKey = state.focusedItemKey
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
focusedItemKey: newFocusedKey
|
||||
}
|
||||
}
|
||||
|
||||
case 'INIT_SCROLL':
|
||||
return {
|
||||
...state,
|
||||
scrollTrigger: 'initial',
|
||||
lastScrollOffset: 0
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Model } from '@renderer/types'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
// 列表项类型,组名也作为列表项
|
||||
export type ListItemType = 'group' | 'model'
|
||||
|
||||
// 滚动触发来源类型
|
||||
export type ScrollTrigger = 'initial' | 'search' | 'keyboard' | 'none'
|
||||
|
||||
// 扁平化列表项接口
|
||||
export interface FlatListItem {
|
||||
key: string
|
||||
type: ListItemType
|
||||
icon?: ReactNode
|
||||
name: ReactNode
|
||||
tags?: ReactNode
|
||||
model?: Model
|
||||
isPinned?: boolean
|
||||
isSelected?: boolean
|
||||
}
|
||||
|
||||
// 滚动和焦点相关的状态类型
|
||||
export interface ScrollState {
|
||||
focusedItemKey: string
|
||||
scrollTrigger: ScrollTrigger
|
||||
lastScrollOffset: number
|
||||
stickyGroup: FlatListItem | null
|
||||
isMouseOver: boolean
|
||||
}
|
||||
|
||||
// 滚动和焦点相关的 action 类型
|
||||
export type ScrollAction =
|
||||
| { type: 'SET_FOCUSED_ITEM_KEY'; payload: string }
|
||||
| { type: 'SET_SCROLL_TRIGGER'; payload: ScrollTrigger }
|
||||
| { type: 'SET_LAST_SCROLL_OFFSET'; payload: number }
|
||||
| { type: 'SET_STICKY_GROUP'; payload: FlatListItem | null }
|
||||
| { type: 'SET_IS_MOUSE_OVER'; payload: boolean }
|
||||
| { type: 'FOCUS_NEXT_ITEM'; payload: { modelItems: FlatListItem[]; step: number } }
|
||||
| { type: 'FOCUS_PAGE'; payload: { modelItems: FlatListItem[]; currentIndex: number; step: number } }
|
||||
| { type: 'SEARCH_CHANGED'; payload: { searchText: string } }
|
||||
| { type: 'UPDATE_ON_LIST_CHANGE'; payload: { modelItems: FlatListItem[] } }
|
||||
| { type: 'INIT_SCROLL'; payload?: void }
|
||||
@@ -1,41 +1,68 @@
|
||||
import { Search } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BarLoader from 'react-spinners/BarLoader'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
text: string
|
||||
}
|
||||
|
||||
// Define variants for the spinner animation
|
||||
const spinnerVariants = {
|
||||
defaultColor: {
|
||||
color: '#2a2a2a'
|
||||
},
|
||||
dimmed: {
|
||||
color: '#8C9296'
|
||||
}
|
||||
}
|
||||
|
||||
export default function Spinner({ text }: Props) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Container>
|
||||
<Search size={24} />
|
||||
<StatusText>{t(text)}</StatusText>
|
||||
<BarLoader color="#1677ff" />
|
||||
</Container>
|
||||
<Searching
|
||||
variants={spinnerVariants}
|
||||
initial="defaultColor"
|
||||
animate={['defaultColor', 'dimmed']}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
repeat: Infinity,
|
||||
repeatType: 'reverse',
|
||||
ease: 'easeInOut'
|
||||
}}>
|
||||
<Search size={16} style={{ color: 'unset' }} />
|
||||
<span>{t(text)}</span>
|
||||
</Searching>
|
||||
)
|
||||
}
|
||||
|
||||
const baseContainer = css`
|
||||
// const baseContainer = css`
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// `
|
||||
|
||||
// const Container = styled.div`
|
||||
// ${baseContainer}
|
||||
// background-color: var(--color-background-mute);
|
||||
// padding: 10px;
|
||||
// border-radius: 10px;
|
||||
// margin-bottom: 10px;
|
||||
// gap: 10px;
|
||||
// `
|
||||
|
||||
// const StatusText = styled.div`
|
||||
// font-size: 14px;
|
||||
// line-height: 1.6;
|
||||
// text-decoration: none;
|
||||
// color: var(--color-text-1);
|
||||
// `
|
||||
const SearchWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
${baseContainer}
|
||||
background-color: var(--color-background-mute);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
`
|
||||
|
||||
const StatusText = styled.div`
|
||||
gap: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
text-decoration: none;
|
||||
color: var(--color-text-1);
|
||||
padding: 10px;
|
||||
padding-left: 0;
|
||||
`
|
||||
const Searching = motion.create(SearchWrapper)
|
||||
|
||||
@@ -35,7 +35,6 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
||||
}
|
||||
|
||||
const handleTranslate = async () => {
|
||||
console.log('handleTranslate', text)
|
||||
if (!text?.trim()) return
|
||||
|
||||
if (!(await translateConfirm())) {
|
||||
|
||||
@@ -254,6 +254,8 @@ export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMet
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
width={800}
|
||||
centered
|
||||
transitionName="animation-move-down"
|
||||
footer={[
|
||||
<Button key="refresh" icon={<ReloadOutlined />} onClick={fetchBackupFiles} disabled={loading}>
|
||||
{t('settings.data.webdav.backup.manager.refresh')}
|
||||
|
||||
@@ -76,7 +76,9 @@ export function WebdavBackupModal({
|
||||
open={isModalVisible}
|
||||
onOk={handleBackup}
|
||||
onCancel={handleCancel}
|
||||
okButtonProps={{ loading: backuping }}>
|
||||
okButtonProps={{ loading: backuping }}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Input
|
||||
value={customFileName}
|
||||
onChange={(e) => setCustomFileName(e.target.value)}
|
||||
@@ -206,7 +208,9 @@ export function WebdavRestoreModal({
|
||||
onOk={handleRestore}
|
||||
onCancel={handleCancel}
|
||||
okButtonProps={{ loading: restoring }}
|
||||
width={600}>
|
||||
width={600}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import Logger from 'electron-log/renderer'
|
||||
|
||||
// 设置渲染进程的日志级别
|
||||
Logger.transports.console.level = 'info'
|
||||
|
||||
export default Logger
|
||||
@@ -469,8 +469,4 @@ function updateDefaultMinApps(param) {
|
||||
DEFAULT_MIN_APPS = param
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('DEFAULT_MIN_APPS', DEFAULT_MIN_APPS)
|
||||
}
|
||||
|
||||
export { DEFAULT_MIN_APPS, loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps }
|
||||
|
||||
@@ -189,7 +189,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.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*|.*\bglm-zero-preview\b.*|.*\bgrok-3-mini(?:-[\w-]+)?\b.*)$/i
|
||||
/^(o\d+(?:-[\w-]+)?|.*\b(?:reasoning|reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*|.*\bhunyuan-t1(?:-[\w-]+)?\b.*|.*\bglm-zero-preview\b.*|.*\bgrok-3-mini(?:-[\w-]+)?\b.*)$/i
|
||||
|
||||
// Embedding models
|
||||
export const EMBEDDING_REGEX =
|
||||
@@ -206,7 +206,7 @@ export const FUNCTION_CALLING_MODELS = [
|
||||
'gpt-4o-mini',
|
||||
'gpt-4',
|
||||
'gpt-4.5',
|
||||
'o1(?:-[\\w-]+)?',
|
||||
'o(1|3|4)(?:-[\\w-]+)?',
|
||||
'claude',
|
||||
'qwen',
|
||||
'qwen3',
|
||||
@@ -2153,11 +2153,11 @@ export const TEXT_TO_IMAGES_MODELS_SUPPORT_IMAGE_ENHANCEMENT = [
|
||||
|
||||
export const GENERATE_IMAGE_MODELS = [
|
||||
'gemini-2.0-flash-exp-image-generation',
|
||||
'gemini-2.0-flash-preview-image-generation',
|
||||
'gemini-2.0-flash-exp',
|
||||
'grok-2-image-1212',
|
||||
'grok-2-image',
|
||||
'grok-2-image-latest',
|
||||
'gpt-4o-image',
|
||||
'gpt-image-1'
|
||||
]
|
||||
|
||||
@@ -2172,6 +2172,7 @@ export const GEMINI_SEARCH_MODELS = [
|
||||
'gemini-2.5-pro-exp-03-25',
|
||||
'gemini-2.5-pro-preview',
|
||||
'gemini-2.5-pro-preview-03-25',
|
||||
'gemini-2.5-pro-preview-05-06',
|
||||
'gemini-2.5-flash-preview',
|
||||
'gemini-2.5-flash-preview-04-17'
|
||||
]
|
||||
@@ -2605,7 +2606,7 @@ export const THINKING_TOKEN_MAP: Record<string, { min: number; max: number }> =
|
||||
|
||||
export const findTokenLimit = (modelId: string): { min: number; max: number } | undefined => {
|
||||
for (const [pattern, limits] of Object.entries(THINKING_TOKEN_MAP)) {
|
||||
if (new RegExp(pattern).test(modelId)) {
|
||||
if (new RegExp(pattern, 'i').test(modelId)) {
|
||||
return limits
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,8 @@ export function getProviderLogo(providerId: string) {
|
||||
return PROVIDER_LOGO_MAP[providerId as keyof typeof PROVIDER_LOGO_MAP]
|
||||
}
|
||||
|
||||
export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina', 'voyageai', 'dashscope', 'aihubmix']
|
||||
// export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina', 'voyageai', 'dashscope', 'aihubmix']
|
||||
export const NOT_SUPPORTED_REANK_PROVIDERS = ['ollama']
|
||||
|
||||
export const PROVIDER_CONFIG = {
|
||||
openai: {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import BochaLogo from '@renderer/assets/images/search/bocha.webp'
|
||||
import ExaLogo from '@renderer/assets/images/search/exa.png'
|
||||
import SearxngLogo from '@renderer/assets/images/search/searxng.svg'
|
||||
import TavilyLogo from '@renderer/assets/images/search/tavily.png'
|
||||
|
||||
export function getWebSearchProviderLogo(providerId: string) {
|
||||
switch (providerId) {
|
||||
case 'tavily':
|
||||
@@ -9,6 +11,8 @@ export function getWebSearchProviderLogo(providerId: string) {
|
||||
return SearxngLogo
|
||||
case 'exa':
|
||||
return ExaLogo
|
||||
case 'bocha':
|
||||
return BochaLogo
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
@@ -32,6 +36,12 @@ export const WEB_SEARCH_PROVIDER_CONFIG = {
|
||||
apiKey: 'https://dashboard.exa.ai/api-keys'
|
||||
}
|
||||
},
|
||||
bocha: {
|
||||
websites: {
|
||||
official: 'https://bochaai.com',
|
||||
apiKey: 'https://open.bochaai.com/overview'
|
||||
}
|
||||
},
|
||||
'local-google': {
|
||||
websites: {
|
||||
official: 'https://www.google.com'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Logger from '@renderer/config/logger'
|
||||
import type { LegacyMessage as OldMessage, Topic } from '@renderer/types'
|
||||
import { FileTypes } from '@renderer/types' // Import FileTypes enum
|
||||
import { WebSearchSource } from '@renderer/types'
|
||||
@@ -90,7 +91,7 @@ function mapOldStatusToNewMessageStatus(oldStatus: OldMessage['status']): NewMes
|
||||
|
||||
// --- UPDATED UPGRADE FUNCTION for Version 7 ---
|
||||
export async function upgradeToV7(tx: Transaction): Promise<void> {
|
||||
console.log('Starting DB migration to version 7: Normalizing messages and blocks...')
|
||||
Logger.info('Starting DB migration to version 7: Normalizing messages and blocks...')
|
||||
|
||||
const oldTopicsTable = tx.table('topics')
|
||||
const newBlocksTable = tx.table('message_blocks')
|
||||
@@ -303,8 +304,8 @@ export async function upgradeToV7(tx: Transaction): Promise<void> {
|
||||
const updateOperations = Object.entries(topicUpdates).map(([id, data]) => ({ key: id, changes: data }))
|
||||
if (updateOperations.length > 0) {
|
||||
await oldTopicsTable.bulkUpdate(updateOperations)
|
||||
console.log(`Updated message references for ${updateOperations.length} topics.`)
|
||||
Logger.log(`Updated message references for ${updateOperations.length} topics.`)
|
||||
}
|
||||
|
||||
console.log('DB migration to version 7 finished successfully.')
|
||||
Logger.log('DB migration to version 7 finished successfully.')
|
||||
}
|
||||
|
||||
@@ -18,17 +18,7 @@ import useUpdateHandler from './useUpdateHandler'
|
||||
|
||||
export function useAppInit() {
|
||||
const dispatch = useAppDispatch()
|
||||
const {
|
||||
proxyUrl,
|
||||
language,
|
||||
windowStyle,
|
||||
autoCheckUpdate,
|
||||
proxyMode,
|
||||
customCss,
|
||||
enableDataCollection,
|
||||
setZoomFactor,
|
||||
zoomFactor
|
||||
} = useSettings()
|
||||
const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss, enableDataCollection } = useSettings()
|
||||
const { minappShow } = useRuntime()
|
||||
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
|
||||
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
|
||||
@@ -41,19 +31,6 @@ export function useAppInit() {
|
||||
avatar?.value && dispatch(setAvatar(avatar.value))
|
||||
}, [avatar, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
const removeZoomListener = window.api.onZoomFactorUpdate((factor) => {
|
||||
setZoomFactor(factor)
|
||||
})
|
||||
return () => {
|
||||
removeZoomListener()
|
||||
}
|
||||
}, [setZoomFactor])
|
||||
|
||||
useEffect(() => {
|
||||
setZoomFactor(zoomFactor)
|
||||
}, [setZoomFactor, zoomFactor])
|
||||
|
||||
useEffect(() => {
|
||||
document.getElementById('spinner')?.remove()
|
||||
runAsyncFunction(async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSelector } from '@reduxjs/toolkit'
|
||||
import Logger from '@renderer/config/logger'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { estimateUserPromptUsage } from '@renderer/services/TokenService'
|
||||
import store, { type RootState, useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
@@ -79,7 +80,7 @@ export function useMessageOperations(topic: Topic) {
|
||||
)
|
||||
|
||||
/**
|
||||
* 编辑消息。(目前仅更新 Redux state)。 / Edits a message. (Currently only updates Redux state).
|
||||
* 编辑消息。 / Edits a message.
|
||||
* 使用 newMessagesActions.updateMessage.
|
||||
*/
|
||||
const editMessage = useCallback(
|
||||
@@ -88,21 +89,15 @@ export function useMessageOperations(topic: Topic) {
|
||||
console.error('[editMessage] Topic prop is not valid.')
|
||||
return
|
||||
}
|
||||
console.log(`[useMessageOperations] Editing message ${messageId} with updates:`, updates)
|
||||
|
||||
const messageUpdates: Partial<Message> & Pick<Message, 'id'> = {
|
||||
id: messageId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
...updates
|
||||
}
|
||||
|
||||
// Call the thunk with topic.id and only message updates
|
||||
const success = await dispatch(updateMessageAndBlocksThunk(topic.id, messageUpdates, []))
|
||||
|
||||
if (success) {
|
||||
console.log(`[useMessageOperations] Successfully edited message ${messageId} properties.`)
|
||||
} else {
|
||||
console.error(`[useMessageOperations] Failed to edit message ${messageId} properties.`)
|
||||
}
|
||||
await dispatch(updateMessageAndBlocksThunk(topic.id, messageUpdates, []))
|
||||
},
|
||||
[dispatch, topic.id]
|
||||
)
|
||||
@@ -133,9 +128,16 @@ export function useMessageOperations(topic: Topic) {
|
||||
const files = findFileBlocks(message).map((block) => block.file)
|
||||
|
||||
const usage = await estimateUserPromptUsage({ content: editedContent, files })
|
||||
const messageUpdates: Partial<Message> & Pick<Message, 'id'> = {
|
||||
id: message.id,
|
||||
updatedAt: new Date().toISOString(),
|
||||
usage
|
||||
}
|
||||
|
||||
await dispatch(updateMessageAndBlocksThunk(topic.id, { id: message.id, usage }, []))
|
||||
|
||||
await dispatch(
|
||||
newMessagesActions.updateMessage({ topicId: topic.id, messageId: message.id, updates: messageUpdates })
|
||||
)
|
||||
// 对于message的修改会在下面的thunk中保存
|
||||
await dispatch(resendUserMessageWithEditThunk(topic.id, message, mainTextBlockId, editedContent, assistant))
|
||||
},
|
||||
[dispatch, topic.id]
|
||||
@@ -272,12 +274,10 @@ export function useMessageOperations(topic: Topic) {
|
||||
}
|
||||
dispatch(updateOneBlock({ id: blockId, changes }))
|
||||
await dispatch(updateTranslationBlockThunk(blockId, '', false))
|
||||
console.log('[getTranslationUpdater] update existing translation block:', blockId)
|
||||
} else {
|
||||
blockId = await dispatch(
|
||||
initiateTranslationThunk(messageId, topic.id, targetLanguage, sourceBlockId, sourceLanguage)
|
||||
)
|
||||
console.log('[getTranslationUpdater] create new translation block:', blockId)
|
||||
}
|
||||
|
||||
if (!blockId) {
|
||||
@@ -302,7 +302,7 @@ export function useMessageOperations(topic: Topic) {
|
||||
*/
|
||||
const createTopicBranch = useCallback(
|
||||
(sourceTopicId: string, branchPointIndex: number, newTopic: Topic) => {
|
||||
console.log(`Cloning messages from topic ${sourceTopicId} to new topic ${newTopic.id}`)
|
||||
Logger.log(`Cloning messages from topic ${sourceTopicId} to new topic ${newTopic.id}`)
|
||||
return dispatch(cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic))
|
||||
},
|
||||
[dispatch]
|
||||
@@ -313,31 +313,55 @@ export function useMessageOperations(topic: Topic) {
|
||||
* Uses the generalized thunk for persistence.
|
||||
*/
|
||||
const editMessageBlocks = useCallback(
|
||||
// messageId?: string
|
||||
async (blockUpdatesListRaw: Partial<MessageBlock>[]) => {
|
||||
async (messageId: string, updates: Partial<MessageBlock>) => {
|
||||
if (!topic?.id) {
|
||||
console.error('[editMessageBlocks] Topic prop is not valid.')
|
||||
return
|
||||
}
|
||||
if (!blockUpdatesListRaw || blockUpdatesListRaw.length === 0) {
|
||||
console.warn('[editMessageBlocks] Received empty block updates list.')
|
||||
|
||||
const blockUpdatesListProcessed = {
|
||||
updatedAt: new Date().toISOString(),
|
||||
...updates
|
||||
}
|
||||
|
||||
const messageUpdates: Partial<Message> & Pick<Message, 'id'> = {
|
||||
id: messageId,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
await dispatch(updateMessageAndBlocksThunk(topic.id, messageUpdates, [blockUpdatesListProcessed]))
|
||||
},
|
||||
[dispatch, topic.id]
|
||||
)
|
||||
|
||||
/**
|
||||
* Removes a specific block from a message.
|
||||
*/
|
||||
const removeMessageBlock = useCallback(
|
||||
async (messageId: string, blockIdToRemove: string) => {
|
||||
if (!topic?.id) {
|
||||
console.error('[removeMessageBlock] Topic prop is not valid.')
|
||||
return
|
||||
}
|
||||
|
||||
const blockUpdatesListProcessed = blockUpdatesListRaw.map((update) => ({
|
||||
...update,
|
||||
updatedAt: new Date().toISOString()
|
||||
}))
|
||||
|
||||
const success = await dispatch(updateMessageAndBlocksThunk(topic.id, null, blockUpdatesListProcessed))
|
||||
|
||||
if (success) {
|
||||
// console.log(`[useMessageOperations] Successfully processed block updates for message ${messageId}.`)
|
||||
} else {
|
||||
// console.error(`[useMessageOperations] Failed to process block updates for message ${messageId}.`)
|
||||
const state = store.getState()
|
||||
const message = state.messages.entities[messageId]
|
||||
if (!message || !message.blocks) {
|
||||
console.error('[removeMessageBlock] Message not found or has no blocks:', messageId)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedBlocks = message.blocks.filter((blockId) => blockId !== blockIdToRemove)
|
||||
|
||||
const messageUpdates: Partial<Message> & Pick<Message, 'id'> = {
|
||||
id: messageId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
blocks: updatedBlocks
|
||||
}
|
||||
|
||||
await dispatch(updateMessageAndBlocksThunk(topic.id, messageUpdates, []))
|
||||
},
|
||||
[dispatch, topic.id]
|
||||
[dispatch, topic?.id]
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -355,7 +379,8 @@ export function useMessageOperations(topic: Topic) {
|
||||
resumeMessage,
|
||||
getTranslationUpdater,
|
||||
createTopicBranch,
|
||||
editMessageBlocks
|
||||
editMessageBlocks,
|
||||
removeMessageBlock
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import db from '@renderer/databases'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { sortBy } from 'lodash'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { useProviders } from './useProvider'
|
||||
|
||||
export const usePinnedModels = () => {
|
||||
const [pinnedModels, setPinnedModels] = useState<string[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { providers } = useProviders()
|
||||
|
||||
useEffect(() => {
|
||||
const loadPinnedModels = async () => {
|
||||
setLoading(true)
|
||||
const setting = await db.settings.get('pinned:models')
|
||||
const savedPinnedModels = setting?.value || []
|
||||
|
||||
// Filter out invalid pinned models
|
||||
const allModelIds = providers.flatMap((p) => p.models || []).map((m) => getModelUniqId(m))
|
||||
const validPinnedModels = savedPinnedModels.filter((id) => allModelIds.includes(id))
|
||||
|
||||
// Update storage if there were invalid models
|
||||
if (validPinnedModels.length !== savedPinnedModels.length) {
|
||||
await db.settings.put({ id: 'pinned:models', value: validPinnedModels })
|
||||
}
|
||||
|
||||
setPinnedModels(sortBy(validPinnedModels))
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadPinnedModels().catch((error) => {
|
||||
console.error('Failed to load pinned models', error)
|
||||
setPinnedModels([])
|
||||
setLoading(false)
|
||||
})
|
||||
}, [providers])
|
||||
|
||||
const updatePinnedModels = useCallback(async (models: string[]) => {
|
||||
await db.settings.put({ id: 'pinned:models', value: models })
|
||||
setPinnedModels(sortBy(models))
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Toggle a single pinned model
|
||||
* @param modelId - The ID string of the model to toggle
|
||||
*/
|
||||
const togglePinnedModel = useCallback(
|
||||
async (modelId: string) => {
|
||||
try {
|
||||
const newPinnedModels = pinnedModels.includes(modelId)
|
||||
? pinnedModels.filter((id) => id !== modelId)
|
||||
: [...pinnedModels, modelId]
|
||||
await updatePinnedModels(newPinnedModels)
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle pinned model', error)
|
||||
}
|
||||
},
|
||||
[pinnedModels, updatePinnedModels]
|
||||
)
|
||||
|
||||
return { pinnedModels, updatePinnedModels, togglePinnedModel, loading }
|
||||
}
|
||||
@@ -14,8 +14,7 @@ import {
|
||||
setTopicPosition,
|
||||
setTray as _setTray,
|
||||
setTrayOnClose,
|
||||
setWindowStyle,
|
||||
setZoomFactor
|
||||
setWindowStyle
|
||||
} from '@renderer/store/settings'
|
||||
import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
||||
|
||||
@@ -80,10 +79,6 @@ export function useSettings() {
|
||||
},
|
||||
setAssistantIconType(assistantIconType: AssistantIconType) {
|
||||
dispatch(setAssistantIconType(assistantIconType))
|
||||
},
|
||||
setZoomFactor(factor: number) {
|
||||
dispatch(setZoomFactor(factor))
|
||||
window.api.setZoomFactor(factor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,14 +107,6 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) =>
|
||||
// Convert class to object with functions since class only has static methods
|
||||
// 只有静态方法,没必要用class,可以export {}
|
||||
export const TopicManager = {
|
||||
async getTopicLimit(limit: number) {
|
||||
return await db.topics
|
||||
.orderBy('updatedAt') // 按 updatedAt 排序(默认升序)
|
||||
.reverse() // 逆序(变成降序)
|
||||
.limit(limit) // 取前 10 条
|
||||
.toArray()
|
||||
},
|
||||
|
||||
async getTopic(id: string) {
|
||||
return await db.topics.get(id)
|
||||
},
|
||||
|
||||
@@ -84,7 +84,18 @@
|
||||
"settings.knowledge_base.recognition.tip": "The assistant will use the large model's intent recognition capability to determine whether to use the knowledge base for answering. This feature will depend on the model's capabilities",
|
||||
"settings.knowledge_base.recognition": "Use Knowledge Base",
|
||||
"settings.knowledge_base.recognition.off": "Force Search",
|
||||
"settings.knowledge_base.recognition.on": "Intent Recognition"
|
||||
"settings.knowledge_base.recognition.on": "Intent Recognition",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Regular Phrase",
|
||||
"add": "Add Phrase",
|
||||
"edit": "Edit Phrase",
|
||||
"delete": "Delete Phrase",
|
||||
"deleteConfirm": "Are you sure to delete this phrase?",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Enter title",
|
||||
"contentLabel": "Content",
|
||||
"contentPlaceholder": "Please enter phrase content, support using variables, and press Tab to quickly locate the variable to modify. For example: \nHelp me plan a route from ${from} to ${to}, and send it to ${email}."
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"error": "API key automatically obtained failed, please get it manually",
|
||||
@@ -624,7 +635,9 @@
|
||||
"error.siyuan.no_config": "Siyuan Note API address or token is not configured",
|
||||
"success.siyuan.export": "Successfully exported to Siyuan Note",
|
||||
"warn.yuque.exporting": "Exporting to Yuque, please do not request export repeatedly!",
|
||||
"warn.siyuan.exporting": "Exporting to Siyuan Note, please do not request export repeatedly!"
|
||||
"warn.siyuan.exporting": "Exporting to Siyuan Note, please do not request export repeatedly!",
|
||||
"download.success": "Download successfully",
|
||||
"download.failed": "Download failed"
|
||||
},
|
||||
"minapp": {
|
||||
"popup": {
|
||||
@@ -707,6 +720,7 @@
|
||||
"pinned": "Pinned",
|
||||
"rerank_model": "Reordering Model",
|
||||
"rerank_model_support_provider": "Currently, the reordering model only supports some providers ({{provider}})",
|
||||
"rerank_model_not_support_provider": "Currently, the reordering model does not support this provider ({{provider}})",
|
||||
"rerank_model_tooltip": "Click the Manage button in Settings -> Model Services to add.",
|
||||
"search": "Search models...",
|
||||
"stream_output": "Stream output",
|
||||
@@ -763,6 +777,8 @@
|
||||
"proxy_required": "Currently, you need to open a proxy to view the generated images, it will be supported in the future",
|
||||
"image_file_required": "Please upload an image first",
|
||||
"image_file_retry": "Please re-upload an image first",
|
||||
"image_placeholder": "No image available",
|
||||
"image_retry": "Retry",
|
||||
"mode": {
|
||||
"generate": "Draw",
|
||||
"edit": "Edit",
|
||||
@@ -1345,6 +1361,7 @@
|
||||
"providerPlaceholder": "Provider name",
|
||||
"advancedSettings": "Advanced Settings"
|
||||
},
|
||||
"messages.prompt": "Show prompt",
|
||||
"messages.divider": "Show divider between messages",
|
||||
"messages.grid_columns": "Message grid display columns",
|
||||
"messages.grid_popover_trigger": "Grid detail trigger",
|
||||
@@ -1375,6 +1392,7 @@
|
||||
"models.add.group_name.tooltip": "Optional e.g. ChatGPT",
|
||||
"models.add.model_id": "Model ID",
|
||||
"models.add.model_id.placeholder": "Required e.g. gpt-3.5-turbo",
|
||||
"models.add.model_id.select.placeholder": "Select Model",
|
||||
"models.add.model_id.tooltip": "Example: gpt-3.5-turbo",
|
||||
"models.add.model_name": "Model Name",
|
||||
"models.add.model_name.placeholder": "Optional e.g. GPT-4",
|
||||
@@ -1386,7 +1404,10 @@
|
||||
"models.check.enabled": "Enabled",
|
||||
"models.check.failed": "Failed",
|
||||
"models.check.keys_status_count": "Passed: {{count_passed}} keys, failed: {{count_failed}} keys",
|
||||
"models.check.model_status_summary": "{{provider}}: {{count_passed}} models passed health checks ({{count_partial}} models had inaccessible keys), {{count_failed}} models completely inaccessible.",
|
||||
"models.check.model_status_failed": "{{count}} models completely inaccessible",
|
||||
"models.check.model_status_partial": "{{count}} models had inaccessible keys",
|
||||
"models.check.model_status_passed": "{{count}} models passed health checks",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "No API keys found, please add API keys first.",
|
||||
"models.check.passed": "Passed",
|
||||
"models.check.select_api_key": "Select the API key to use:",
|
||||
@@ -1532,72 +1553,91 @@
|
||||
"theme.window.style.opaque": "Opaque Window",
|
||||
"theme.window.style.title": "Window Style",
|
||||
"theme.window.style.transparent": "Transparent Window",
|
||||
"zoom.title": "Page Zoom",
|
||||
"title": "Settings",
|
||||
"tool": {
|
||||
"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 newlines)\nPattern matching: *://*.example.com/*\nRegular expression: /example\\.(net|org)/",
|
||||
"check": "Check",
|
||||
"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",
|
||||
"search_provider": "Search service provider",
|
||||
"search_provider_placeholder": "Choose a search service provider.",
|
||||
"subscribe": "Blacklist Subscription",
|
||||
"subscribe_update": "Update",
|
||||
"subscribe_add": "Add Subscription",
|
||||
"subscribe_url": "Subscription Url",
|
||||
"subscribe_name": "Alternative name",
|
||||
"subscribe_name.placeholder": "Alternative name used when the downloaded subscription feed has no name.",
|
||||
"subscribe_add_success": "Subscription feed added successfully!",
|
||||
"subscribe_delete": "Delete",
|
||||
"search_result_default": "Default",
|
||||
"search_with_time": "Search with dates included",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API Key",
|
||||
"api_key.placeholder": "Enter Tavily API Key",
|
||||
"description": "Tavily is a search engine tailored for AI agents, delivering real-time, accurate results, intelligent query suggestions, and in-depth research capabilities.",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Web Search",
|
||||
"apikey": "API Key",
|
||||
"free": "Free",
|
||||
"content_limit": "Content length limit",
|
||||
"content_limit_tooltip": "Limit the content length of search results, content exceeding the limit will be truncated"
|
||||
},
|
||||
"ocr": {
|
||||
"title": "OCR",
|
||||
"provider": "OCR Provider",
|
||||
"provider_placeholder": "Choose a OCR provider",
|
||||
"mac_system_ocr_options": {
|
||||
"mode": {
|
||||
"title": "Recognition Mode",
|
||||
"accurate": "Accurate",
|
||||
"fast": "Fast"
|
||||
},
|
||||
"min_confidence": "Minimum Confidence"
|
||||
}
|
||||
},
|
||||
"title": "Tool Settings"
|
||||
},
|
||||
"topic.position": "Topic position",
|
||||
"topic.position.left": "Left",
|
||||
"topic.position.right": "Right",
|
||||
"topic.show.time": "Show topic time",
|
||||
"tray.onclose": "Minimize to Tray on Close",
|
||||
"tray.show": "Show Tray Icon",
|
||||
"tray.title": "Tray"
|
||||
"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 newlines)\nPattern matching: *://*.example.com/*\nRegular expression: /example\\.(net|org)/",
|
||||
"check": "Check",
|
||||
"check_failed": "Verification failed",
|
||||
"check_success": "Verification successful",
|
||||
"get_api_key": "Get API Key",
|
||||
"no_provider_selected": "Please select a search service provider before checking.",
|
||||
"search_max_result": "Number of search results",
|
||||
"search_provider": "Search service provider",
|
||||
"search_provider_placeholder": "Choose a search service provider.",
|
||||
"search_result_default": "Default",
|
||||
"search_with_time": "Search with dates included",
|
||||
"tavily": {
|
||||
"api_key": "Tavily API Key",
|
||||
"api_key.placeholder": "Enter Tavily API Key",
|
||||
"description": "Tavily is a search engine tailored for AI agents, delivering real-time, accurate results, intelligent query suggestions, and in-depth research capabilities.",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Web Search",
|
||||
"subscribe": "Blacklist Subscription",
|
||||
"subscribe_update": "Update",
|
||||
"subscribe_add": "Add Subscription",
|
||||
"subscribe_url": "Subscription Url",
|
||||
"subscribe_name": "Alternative name",
|
||||
"subscribe_name.placeholder": "Alternative name used when the downloaded subscription feed has no name.",
|
||||
"subscribe_add_success": "Subscription feed added successfully!",
|
||||
"subscribe_delete": "Delete",
|
||||
"overwrite": "Override search service",
|
||||
"overwrite_tooltip": "Force use search service instead of LLM",
|
||||
"apikey": "API key",
|
||||
"free": "Free",
|
||||
"content_limit": "Content length limit",
|
||||
"content_limit_tooltip": "Limit the content length of the search results; content that exceeds the limit will be truncated."
|
||||
},
|
||||
"quickPhrase": {
|
||||
"title": "Quick Phrases",
|
||||
"add": "Add Phrase",
|
||||
"edit": "Edit Phrase",
|
||||
"titleLabel": "Title",
|
||||
"contentLabel": "Content",
|
||||
"titlePlaceholder": "Please enter phrase title",
|
||||
"contentPlaceholder": "Please enter phrase content, support using variables, and press Tab to quickly locate the variable to modify. For example: \nHelp me plan a route from ${from} to ${to}, and send it to ${email}.",
|
||||
"delete": "Delete Phrase",
|
||||
"deleteConfirm": "The phrase cannot be recovered after deletion, continue?",
|
||||
"locationLabel": "Add Location",
|
||||
"global": "Global Phrases",
|
||||
"assistant": "Assistant Phrases"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Quick Menu",
|
||||
"close": "Close",
|
||||
"select": "Select",
|
||||
"page": "Page",
|
||||
"confirm": "Confirm",
|
||||
"back": "Back",
|
||||
"forward": "Forward",
|
||||
"multiple": "Multiple Select"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacy Settings",
|
||||
"enable_privacy_mode": "Anonymous reporting of errors and statistics"
|
||||
},
|
||||
"zoom": {
|
||||
"title": "Page Zoom",
|
||||
"reset": "Reset"
|
||||
}
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "Any language",
|
||||
"button.translate": "Translate",
|
||||
"close": "Close",
|
||||
"closed": "Translation closed",
|
||||
"copied": "Translation content copied",
|
||||
"empty": "Translation content is empty",
|
||||
"not.found": "Translation content not found",
|
||||
"confirm": {
|
||||
"content": "Translation will replace the original text, continue?",
|
||||
"title": "Translation Confirmation"
|
||||
|
||||
@@ -81,6 +81,17 @@
|
||||
"settings.reasoning_effort.medium": "普通の思考",
|
||||
"settings.reasoning_effort.default": "デフォルト",
|
||||
"settings.more": "アシスタント設定",
|
||||
"settings.regular_phrases": {
|
||||
"title": "定型プロンプト",
|
||||
"add": "プロンプトを追加",
|
||||
"edit": "プロンプトを編集",
|
||||
"delete": "プロンプトを削除",
|
||||
"deleteConfirm": "このプロンプトを削除してもよろしいですか?",
|
||||
"titleLabel": "タイトル",
|
||||
"titlePlaceholder": "タイトルを入力",
|
||||
"contentLabel": "内容",
|
||||
"contentPlaceholder": "フレーズの内容を入力してください。変数を使用することもできます。変数を使用する場合は、Tabキーを押して変数を選択し、変数を変更してください。例:\n私の名前は${name}です。"
|
||||
},
|
||||
"settings.knowledge_base.recognition.tip": "アシスタントは大規模言語モデルの意図認識能力を使用して、ナレッジベースを参照する必要があるかどうかを判断します。この機能はモデルの能力に依存します",
|
||||
"settings.knowledge_base.recognition": "ナレッジベースの呼び出し",
|
||||
"settings.knowledge_base.recognition.off": "強制検索",
|
||||
@@ -624,7 +635,9 @@
|
||||
"success.siyuan.export": "思源ノートへのエクスポートに成功しました",
|
||||
"warn.yuque.exporting": "語雀にエクスポート中です。重複してエクスポートしないでください!",
|
||||
"warn.siyuan.exporting": "思源ノートにエクスポート中です。重複してエクスポートしないでください!",
|
||||
"error.yuque.no_config": "語雀のAPIアドレスまたはトークンが設定されていません"
|
||||
"error.yuque.no_config": "語雀のAPIアドレスまたはトークンが設定されていません",
|
||||
"download.success": "ダウンロードに成功しました",
|
||||
"download.failed": "ダウンロードに失敗しました"
|
||||
},
|
||||
"minapp": {
|
||||
"popup": {
|
||||
@@ -721,7 +734,8 @@
|
||||
"text": "テキスト",
|
||||
"vision": "画像",
|
||||
"websearch": "ウェブ検索"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "現在、並べ替えモデルはこのプロバイダー ({{provider}}) をサポートしていません。"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "ダイアログを展開",
|
||||
@@ -763,6 +777,8 @@
|
||||
"proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です",
|
||||
"image_file_required": "画像を先にアップロードしてください",
|
||||
"image_file_retry": "画像を先にアップロードしてください",
|
||||
"image_placeholder": "画像がありません",
|
||||
"image_retry": "再試行",
|
||||
"mode": {
|
||||
"generate": "画像生成",
|
||||
"edit": "部分編集",
|
||||
@@ -1343,6 +1359,7 @@
|
||||
"providerPlaceholder": "プロバイダー名",
|
||||
"advancedSettings": "詳細設定"
|
||||
},
|
||||
"messages.prompt": "プロンプト表示",
|
||||
"messages.divider": "メッセージ間に区切り線を表示",
|
||||
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||
"messages.grid_popover_trigger": "グリッド詳細トリガー",
|
||||
@@ -1373,6 +1390,7 @@
|
||||
"models.add.group_name.tooltip": "例:ChatGPT",
|
||||
"models.add.model_id": "モデルID",
|
||||
"models.add.model_id.placeholder": "必須 例:gpt-3.5-turbo",
|
||||
"models.add.model_id.select.placeholder": "モデルを選択",
|
||||
"models.add.model_id.tooltip": "例:gpt-3.5-turbo",
|
||||
"models.add.model_name": "モデル名",
|
||||
"models.add.model_name.placeholder": "例:GPT-3.5",
|
||||
@@ -1384,7 +1402,10 @@
|
||||
"models.check.enabled": "開く",
|
||||
"models.check.failed": "失敗",
|
||||
"models.check.keys_status_count": "合格:{{count_passed}}個のキー、不合格:{{count_failed}}個のキー",
|
||||
"models.check.model_status_summary": "{{provider}}: {{count_passed}} 個のモデルが健康チェックを完了しました({{count_partial}} 個のモデルは一部のキーにアクセスできませんでした)、{{count_failed}} 個のモデルは完全にアクセスできませんでした。",
|
||||
"models.check.model_status_failed": "{{count}} 個のモデルが完全にアクセスできません",
|
||||
"models.check.model_status_partial": "{{count}} 個のモデルが一部のキーでアクセスできません",
|
||||
"models.check.model_status_passed": "{{count}} 個のモデルが健康チェックを通過しました",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "APIキーが見つかりません。まずAPIキーを追加してください。",
|
||||
"models.check.passed": "成功",
|
||||
"models.check.select_api_key": "使用するAPIキーを選択:",
|
||||
@@ -1530,7 +1551,6 @@
|
||||
"theme.window.style.opaque": "不透明ウィンドウ",
|
||||
"theme.window.style.title": "ウィンドウスタイル",
|
||||
"theme.window.style.transparent": "透明ウィンドウ",
|
||||
"zoom.title": "ページズーム",
|
||||
"title": "設定",
|
||||
"tool": {
|
||||
"title": "ツール設定",
|
||||
@@ -1589,7 +1609,10 @@
|
||||
"titlePlaceholder": "フレーズのタイトルを入力してください",
|
||||
"contentPlaceholder": "フレーズの内容を入力してください。変数を使用することもできます。変数を使用する場合は、Tabキーを押して変数を選択し、変数を変更してください。例:\n私の名前は${name}です。",
|
||||
"delete": "フレーズを削除",
|
||||
"deleteConfirm": "削除後は復元できません。続行しますか?"
|
||||
"deleteConfirm": "削除後は復元できません。続行しますか?",
|
||||
"locationLabel": "追加場所",
|
||||
"global": "グローバルクイックフレーズ",
|
||||
"assistant": "アシスタントプロンプト"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "クイックメニュー",
|
||||
@@ -1605,12 +1628,20 @@
|
||||
"title": "プライバシー設定",
|
||||
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
|
||||
},
|
||||
"zoom": {
|
||||
"title": "ページズーム",
|
||||
"reset": "リセット"
|
||||
},
|
||||
"input.show_translate_confirm": "翻訳確認ダイアログを表示"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "任意の言語",
|
||||
"button.translate": "翻訳",
|
||||
"close": "閉じる",
|
||||
"closed": "翻訳は閉じられました",
|
||||
"copied": "翻訳内容がコピーされました",
|
||||
"empty": "翻訳内容が空です",
|
||||
"not.found": "翻訳内容が見つかりません",
|
||||
"confirm": {
|
||||
"content": "翻訳すると元のテキストが上書きされます。続行しますか?",
|
||||
"title": "翻訳確認"
|
||||
|
||||
@@ -84,7 +84,18 @@
|
||||
"settings.knowledge_base.recognition.tip": "Ассистент будет использовать возможности большой модели для распознавания намерений, чтобы определить, нужно ли обращаться к базе знаний для ответа. Эта функция будет зависеть от возможностей модели",
|
||||
"settings.knowledge_base.recognition": "Использование базы знаний",
|
||||
"settings.knowledge_base.recognition.off": "Принудительный поиск",
|
||||
"settings.knowledge_base.recognition.on": "Распознавание намерений"
|
||||
"settings.knowledge_base.recognition.on": "Распознавание намерений",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Регулярные подсказки",
|
||||
"add": "Добавить подсказку",
|
||||
"edit": "Редактировать подсказку",
|
||||
"delete": "Удалить подсказку",
|
||||
"deleteConfirm": "Вы уверены, что хотите удалить эту подсказку?",
|
||||
"titleLabel": "Заголовок",
|
||||
"titlePlaceholder": "Введите заголовок",
|
||||
"contentLabel": "Содержание",
|
||||
"contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, и нажмите Tab для быстрого перехода к переменной для изменения. Например: \nПомоги мне спланировать маршрут от ${from} до ${to} и отправить его на ${email}."
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную",
|
||||
@@ -624,7 +635,9 @@
|
||||
"error.siyuan.no_config": "Не настроен API адрес или токен Siyuan",
|
||||
"success.siyuan.export": "Успешный экспорт в Siyuan",
|
||||
"warn.yuque.exporting": "Экспортируется в Yuque, пожалуйста, не отправляйте повторные запросы!",
|
||||
"warn.siyuan.exporting": "Экспортируется в Siyuan, пожалуйста, не отправляйте повторные запросы!"
|
||||
"warn.siyuan.exporting": "Экспортируется в Siyuan, пожалуйста, не отправляйте повторные запросы!",
|
||||
"download.success": "Скачано успешно",
|
||||
"download.failed": "Скачивание не удалось"
|
||||
},
|
||||
"minapp": {
|
||||
"popup": {
|
||||
@@ -721,7 +734,8 @@
|
||||
"text": "Текст",
|
||||
"vision": "Визуальные",
|
||||
"websearch": "Веб-поисковые"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "В настоящее время модель переупорядочивания не поддерживает этого провайдера ({{provider}})"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Развернуть диалоговое окно",
|
||||
@@ -763,6 +777,8 @@
|
||||
"proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение",
|
||||
"image_file_required": "Пожалуйста, сначала загрузите изображение",
|
||||
"image_file_retry": "Пожалуйста, сначала загрузите изображение",
|
||||
"image_placeholder": "Изображение недоступно",
|
||||
"image_retry": "Попробовать снова",
|
||||
"mode": {
|
||||
"generate": "Рисование",
|
||||
"edit": "Редактирование",
|
||||
@@ -1343,6 +1359,7 @@
|
||||
"providerPlaceholder": "Имя провайдера",
|
||||
"advancedSettings": "Расширенные настройки"
|
||||
},
|
||||
"messages.prompt": "Показывать подсказки",
|
||||
"messages.divider": "Показывать разделитель между сообщениями",
|
||||
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||
"messages.grid_popover_trigger": "Триггер для отображения подробной информации в сетке",
|
||||
@@ -1373,6 +1390,7 @@
|
||||
"models.add.group_name.tooltip": "Необязательно, например, ChatGPT",
|
||||
"models.add.model_id": "ID модели",
|
||||
"models.add.model_id.placeholder": "Обязательно, например, gpt-3.5-turbo",
|
||||
"models.add.model_id.select.placeholder": "Выберите модель",
|
||||
"models.add.model_id.tooltip": "Пример: gpt-3.5-turbo",
|
||||
"models.add.model_name": "Имя модели",
|
||||
"models.add.model_name.placeholder": "Необязательно, например, GPT-4",
|
||||
@@ -1384,7 +1402,10 @@
|
||||
"models.check.enabled": "Включено",
|
||||
"models.check.failed": "Не прошло",
|
||||
"models.check.keys_status_count": "Прошло: {{count_passed}} ключей, Не прошло: {{count_failed}} ключей",
|
||||
"models.check.model_status_summary": "{{provider}}: {{count_passed}} моделей прошли проверку состояния (из них {{count_partial}} моделей недоступны с некоторыми ключами), {{count_failed}} моделей полностью недоступны.",
|
||||
"models.check.model_status_failed": "{{count}} моделей полностью недоступны",
|
||||
"models.check.model_status_partial": "{{count}} моделей недоступны с некоторыми ключами",
|
||||
"models.check.model_status_passed": "{{count}} моделей прошли проверку состояния",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "API ключи не найдены, пожалуйста, добавьте API ключи.",
|
||||
"models.check.passed": "Прошло",
|
||||
"models.check.select_api_key": "Выберите API ключ для использования:",
|
||||
@@ -1530,7 +1551,6 @@
|
||||
"theme.window.style.opaque": "Непрозрачное окно",
|
||||
"theme.window.style.title": "Стиль окна",
|
||||
"theme.window.style.transparent": "Прозрачное окно",
|
||||
"zoom.title": "Масштаб страницы",
|
||||
"title": "Настройки",
|
||||
"tool": {
|
||||
"title": "Настройки инструментов",
|
||||
@@ -1589,7 +1609,10 @@
|
||||
"titlePlaceholder": "Введите заголовок фразы",
|
||||
"contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, и нажмите Tab для быстрого перехода к переменной для изменения. Например: \nПомоги мне спланировать маршрут от ${from} до ${to} и отправить его на ${email}.",
|
||||
"delete": "Удалить фразу",
|
||||
"deleteConfirm": "После удаления фраза не может быть восстановлена, продолжить?"
|
||||
"deleteConfirm": "После удаления фраза не может быть восстановлена, продолжить?",
|
||||
"locationLabel": "Место добавления",
|
||||
"global": "Глобальные быстрые фразы",
|
||||
"assistant": "Подсказки ассистента"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Быстрое меню",
|
||||
@@ -1605,12 +1628,20 @@
|
||||
"title": "Настройки конфиденциальности",
|
||||
"enable_privacy_mode": "Анонимная отчетность об ошибках и статистике"
|
||||
},
|
||||
"zoom": {
|
||||
"title": "Масштаб страницы",
|
||||
"reset": "Сбросить"
|
||||
},
|
||||
"input.show_translate_confirm": "Показать диалоговое окно подтверждения перевода"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "Любой язык",
|
||||
"button.translate": "Перевести",
|
||||
"close": "Закрыть",
|
||||
"closed": "Перевод закрыт",
|
||||
"copied": "Содержимое перевода скопировано",
|
||||
"empty": "Содержимое перевода пусто",
|
||||
"not.found": "Содержимое перевода не найдено",
|
||||
"confirm": {
|
||||
"content": "Перевод заменит исходный текст, продолжить?",
|
||||
"title": "Перевод подтверждение"
|
||||
|
||||
@@ -84,7 +84,18 @@
|
||||
"settings.reasoning_effort.medium": "斟酌",
|
||||
"settings.reasoning_effort.high": "沉思",
|
||||
"settings.reasoning_effort.default": "默认",
|
||||
"settings.more": "助手设置"
|
||||
"settings.more": "助手设置",
|
||||
"settings.regular_phrases": {
|
||||
"title": "常用短语",
|
||||
"add": "添加短语",
|
||||
"edit": "编辑短语",
|
||||
"delete": "删除短语",
|
||||
"deleteConfirm": "确定要删除这个短语吗?",
|
||||
"titleLabel": "标题",
|
||||
"titlePlaceholder": "输入标题",
|
||||
"contentLabel": "内容",
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}。"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"error": "自动获取密钥失败,请手动获取",
|
||||
@@ -624,7 +635,9 @@
|
||||
"error.siyuan.no_config": "未配置思源笔记API地址或令牌",
|
||||
"success.siyuan.export": "导出到思源笔记成功",
|
||||
"warn.yuque.exporting": "正在导出语雀, 请勿重复请求导出!",
|
||||
"warn.siyuan.exporting": "正在导出到思源笔记,请勿重复请求导出!"
|
||||
"warn.siyuan.exporting": "正在导出到思源笔记,请勿重复请求导出!",
|
||||
"download.success": "下载成功",
|
||||
"download.failed": "下载失败"
|
||||
},
|
||||
"minapp": {
|
||||
"popup": {
|
||||
@@ -707,6 +720,7 @@
|
||||
"pinned": "已固定",
|
||||
"rerank_model": "重排模型",
|
||||
"rerank_model_support_provider": "目前重排序模型仅支持部分服务商 ({{provider}})",
|
||||
"rerank_model_not_support_provider": "目前重排序模型不支持该服务商 ({{provider}})",
|
||||
"rerank_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"search": "搜索模型...",
|
||||
"stream_output": "流式输出",
|
||||
@@ -763,6 +777,8 @@
|
||||
"proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连",
|
||||
"image_file_required": "请先上传图片",
|
||||
"image_file_retry": "请重新上传图片",
|
||||
"image_placeholder": "暂无图片",
|
||||
"image_retry": "重试",
|
||||
"mode": {
|
||||
"generate": "绘图",
|
||||
"edit": "编辑",
|
||||
@@ -1345,6 +1361,7 @@
|
||||
"providerPlaceholder": "提供者名称",
|
||||
"advancedSettings": "高级设置"
|
||||
},
|
||||
"messages.prompt": "显示提示词",
|
||||
"messages.divider": "消息分割线",
|
||||
"messages.grid_columns": "消息网格展示列数",
|
||||
"messages.grid_popover_trigger": "网格详情触发",
|
||||
@@ -1375,6 +1392,7 @@
|
||||
"models.add.group_name.tooltip": "例如 ChatGPT",
|
||||
"models.add.model_id": "模型 ID",
|
||||
"models.add.model_id.placeholder": "必填 例如 gpt-3.5-turbo",
|
||||
"models.add.model_id.select.placeholder": "选择模型",
|
||||
"models.add.model_id.tooltip": "例如 gpt-3.5-turbo",
|
||||
"models.add.model_name": "模型名称",
|
||||
"models.add.model_name.placeholder": "例如 GPT-3.5",
|
||||
@@ -1386,7 +1404,10 @@
|
||||
"models.check.enabled": "开启",
|
||||
"models.check.failed": "失败",
|
||||
"models.check.keys_status_count": "通过:{{count_passed}}个密钥,失败:{{count_failed}}个密钥",
|
||||
"models.check.model_status_summary": "{{provider}}: {{count_passed}} 个模型完成健康检测(其中 {{count_partial}} 个模型用某些密钥无法访问),{{count_failed}} 个模型完全无法访问。",
|
||||
"models.check.model_status_failed": "{{count}} 个模型完全无法访问",
|
||||
"models.check.model_status_partial": "其中 {{count}} 个模型用某些密钥无法访问",
|
||||
"models.check.model_status_passed": "{{count}} 个模型通过健康检测",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "未找到API密钥,请先添加API密钥。",
|
||||
"models.check.passed": "通过",
|
||||
"models.check.select_api_key": "选择要使用的API密钥:",
|
||||
@@ -1532,7 +1553,6 @@
|
||||
"theme.window.style.opaque": "不透明窗口",
|
||||
"theme.window.style.title": "窗口样式",
|
||||
"theme.window.style.transparent": "透明窗口",
|
||||
"zoom.title": "页面缩放",
|
||||
"title": "设置",
|
||||
"tool": {
|
||||
"title": "工具设置",
|
||||
@@ -1595,7 +1615,10 @@
|
||||
"titlePlaceholder": "请输入短语标题",
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}。",
|
||||
"delete": "删除短语",
|
||||
"deleteConfirm": "删除短语后将无法恢复,是否继续?"
|
||||
"deleteConfirm": "删除短语后将无法恢复,是否继续?",
|
||||
"locationLabel": "添加位置",
|
||||
"global": "全局短语",
|
||||
"assistant": "助手短语"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "快捷菜单",
|
||||
@@ -1611,6 +1634,10 @@
|
||||
"title": "隐私设置",
|
||||
"enable_privacy_mode": "匿名发送错误报告和数据统计"
|
||||
},
|
||||
"zoom": {
|
||||
"title": "缩放",
|
||||
"reset": "重置"
|
||||
},
|
||||
"topic.position": "话题位置",
|
||||
"topic.position.left": "左侧",
|
||||
"topic.position.right": "右侧",
|
||||
@@ -1623,6 +1650,10 @@
|
||||
"any.language": "任意语言",
|
||||
"button.translate": "翻译",
|
||||
"close": "关闭",
|
||||
"closed": "翻译已关闭",
|
||||
"copied": "翻译内容已复制",
|
||||
"empty": "翻译内容为空",
|
||||
"not.found": "未找到翻译内容",
|
||||
"confirm": {
|
||||
"content": "翻译后将覆盖原文,是否继续?",
|
||||
"title": "翻译确认"
|
||||
|
||||
@@ -81,6 +81,17 @@
|
||||
"settings.reasoning_effort.medium": "正常思考",
|
||||
"settings.reasoning_effort.default": "預設",
|
||||
"settings.more": "助手設定",
|
||||
"settings.regular_phrases": {
|
||||
"title": "常用短语",
|
||||
"add": "添加短语",
|
||||
"edit": "編輯短语",
|
||||
"delete": "刪除短语",
|
||||
"deleteConfirm": "確定要刪除這個短语嗎?",
|
||||
"titleLabel": "標題",
|
||||
"titlePlaceholder": "輸入標題",
|
||||
"contentLabel": "內容",
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}。"
|
||||
},
|
||||
"settings.knowledge_base.recognition.tip": "智慧代理人將調用大語言模型的意圖識別能力,判斷是否需要調用知識庫進行回答,該功能將依賴模型的能力",
|
||||
"settings.knowledge_base.recognition": "調用知識庫",
|
||||
"settings.knowledge_base.recognition.off": "強制檢索",
|
||||
@@ -624,7 +635,9 @@
|
||||
"error.siyuan.no_config": "未配置思源筆記API地址或令牌",
|
||||
"success.siyuan.export": "導出到思源筆記成功",
|
||||
"warn.yuque.exporting": "正在導出語雀,請勿重複請求導出!",
|
||||
"warn.siyuan.exporting": "正在導出到思源筆記,請勿重複請求導出!"
|
||||
"warn.siyuan.exporting": "正在導出到思源筆記,請勿重複請求導出!",
|
||||
"download.success": "下載成功",
|
||||
"download.failed": "下載失敗"
|
||||
},
|
||||
"minapp": {
|
||||
"popup": {
|
||||
@@ -721,7 +734,8 @@
|
||||
"text": "文字",
|
||||
"vision": "視覺",
|
||||
"websearch": "網路搜尋"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "目前,重新排序模型不支援此提供者({{provider}})"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "伸縮對話框",
|
||||
@@ -763,6 +777,8 @@
|
||||
"proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連",
|
||||
"image_file_required": "請先上傳圖片",
|
||||
"image_file_retry": "請重新上傳圖片",
|
||||
"image_placeholder": "無圖片",
|
||||
"image_retry": "重試",
|
||||
"mode": {
|
||||
"generate": "繪圖",
|
||||
"edit": "編輯",
|
||||
@@ -1344,6 +1360,7 @@
|
||||
"providerPlaceholder": "提供者名稱",
|
||||
"advancedSettings": "高級設定"
|
||||
},
|
||||
"messages.prompt": "提示詞顯示",
|
||||
"messages.divider": "訊息間顯示分隔線",
|
||||
"messages.grid_columns": "訊息網格展示列數",
|
||||
"messages.grid_popover_trigger": "網格詳細資訊觸發",
|
||||
@@ -1374,6 +1391,7 @@
|
||||
"models.add.group_name.tooltip": "選填,例如 ChatGPT",
|
||||
"models.add.model_id": "模型 ID",
|
||||
"models.add.model_id.placeholder": "必填,例如 gpt-3.5-turbo",
|
||||
"models.add.model_id.select.placeholder": "選擇模型",
|
||||
"models.add.model_id.tooltip": "例如 gpt-3.5-turbo",
|
||||
"models.add.model_name": "模型名稱",
|
||||
"models.add.model_name.placeholder": "選填,例如 GPT-4",
|
||||
@@ -1385,7 +1403,10 @@
|
||||
"models.check.enabled": "開啟",
|
||||
"models.check.failed": "失敗",
|
||||
"models.check.keys_status_count": "通過:{{count_passed}}個密鑰,失敗:{{count_failed}}個密鑰",
|
||||
"models.check.model_status_summary": "{{provider}}: {{count_passed}} 個模型完成健康檢查(其中 {{count_partial}} 個模型用某些密鑰無法訪問),{{count_failed}} 個模型完全無法訪問。",
|
||||
"models.check.model_status_failed": "{{count}} 個模型完全無法訪問",
|
||||
"models.check.model_status_partial": "其中 {{count}} 個模型用某些密鑰無法訪問",
|
||||
"models.check.model_status_passed": "{{count}} 個模型通過健康檢查",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "未找到API密鑰,請先添加API密鑰。",
|
||||
"models.check.passed": "通過",
|
||||
"models.check.select_api_key": "選擇要使用的API密鑰:",
|
||||
@@ -1531,7 +1552,6 @@
|
||||
"theme.window.style.opaque": "不透明視窗",
|
||||
"theme.window.style.title": "視窗樣式",
|
||||
"theme.window.style.transparent": "透明視窗",
|
||||
"zoom.title": "頁面縮放",
|
||||
"title": "設定",
|
||||
"tool": {
|
||||
"title": "工具",
|
||||
@@ -1590,7 +1610,10 @@
|
||||
"titlePlaceholder": "請輸入短語標題",
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}。",
|
||||
"delete": "刪除短語",
|
||||
"deleteConfirm": "刪除後無法復原,是否繼續?"
|
||||
"deleteConfirm": "刪除後無法復原,是否繼續?",
|
||||
"locationLabel": "添加位置",
|
||||
"global": "全局快速短語",
|
||||
"assistant": "助手提示詞"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "快捷選單",
|
||||
@@ -1605,12 +1628,20 @@
|
||||
"privacy": {
|
||||
"title": "隱私設定",
|
||||
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
|
||||
},
|
||||
"zoom": {
|
||||
"title": "縮放",
|
||||
"reset": "重置"
|
||||
}
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "任意語言",
|
||||
"button.translate": "翻譯",
|
||||
"close": "關閉",
|
||||
"closed": "翻譯已關閉",
|
||||
"copied": "翻譯內容已複製",
|
||||
"empty": "翻譯內容為空",
|
||||
"not.found": "未找到翻譯內容",
|
||||
"confirm": {
|
||||
"content": "翻譯後將覆蓋原文,是否繼續?",
|
||||
"title": "翻譯確認"
|
||||
|
||||
@@ -29,7 +29,26 @@
|
||||
"tag.default": "Προεπιλογή",
|
||||
"tag.new": "Νέος",
|
||||
"tag.system": "Σύστημα",
|
||||
"title": "Ειδικοί"
|
||||
"title": "Ειδικοί",
|
||||
"import": {
|
||||
"type": {
|
||||
"url": "URL",
|
||||
"file": "Αρχείο"
|
||||
},
|
||||
"error": {
|
||||
"url_required": "Παρακαλώ εισάγετε τη διεύθυνση URL",
|
||||
"fetch_failed": "Αποτυχία λήψης δεδομένων από το URL",
|
||||
"invalid_format": "Μη έγκυρη μορφή πράκτορα: λείπουν υποχρεωτικά πεδία"
|
||||
},
|
||||
"title": "Εισαγωγή από το εξωτερικό",
|
||||
"url_placeholder": "Εισάγετε τη διεύθυνση URL JSON",
|
||||
"select_file": "Επιλέξτε αρχείο",
|
||||
"button": "Εισαγωγή",
|
||||
"file_filter": "Αρχεία JSON"
|
||||
},
|
||||
"export": {
|
||||
"agent": "Εξαγωγή υποκειμένου"
|
||||
}
|
||||
},
|
||||
"assistants": {
|
||||
"abbr": "Βοηθός",
|
||||
@@ -52,8 +71,31 @@
|
||||
"settings.reasoning_effort.low": "Μικρό",
|
||||
"settings.reasoning_effort.medium": "Μεσαίο",
|
||||
"settings.reasoning_effort.off": "Απενεργοποίηση",
|
||||
"settings.reasoning_effort.tip": "Υποστηρίζεται μόνο για μοντέλα πληροφορικής OpenAI o-series και Anthropic",
|
||||
"title": "Βοηθός"
|
||||
"title": "Βοηθός",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Δημοφιλείς φράσεις",
|
||||
"add": "Προσθήκη φράσης",
|
||||
"edit": "Επεξεργασία φράσης",
|
||||
"delete": "Διαγραφή φράσης",
|
||||
"deleteConfirm": "Είστε βέβαιος ότι θέλετε να διαγράψετε αυτήν τη φράση;",
|
||||
"titleLabel": "Τίτλος",
|
||||
"titlePlaceholder": "Εισαγάγετε τίτλο",
|
||||
"contentLabel": "Περιεχόμενο",
|
||||
"contentPlaceholder": "Παρακαλώ εισάγετε το περιεχόμενο της φράσης. Υποστηρίζονται μεταβλητές, και στη συνέχεια πατήστε Tab για να μεταβείτε γρήγορα στη μεταβλητή και να την επεξεργαστείτε. Για παράδειγμα: \\nΒοήθησέ με να σχεδιάσω μια διαδρομή από το ${from} στο ${to}, και στη συνέχεια στείλε την στο ${email}."
|
||||
},
|
||||
"settings.title": "Ρυθμίσεις Βοηθού",
|
||||
"icon.type": "Εικόνα Βοηθού",
|
||||
"settings.mcp": "Διακομιστής MCP",
|
||||
"settings.mcp.enableFirst": "Πρώτα ενεργοποιήστε αυτόν τον διακομιστή στις ρυθμίσεις MCP",
|
||||
"settings.mcp.title": "Ρυθμίσεις MCP",
|
||||
"settings.mcp.noServersAvailable": "Δεν υπάρχουν διαθέσιμοι διακομιστές MCP. Προσθέστε ένα διακομιστή στις ρυθμίσεις",
|
||||
"settings.mcp.description": "Διακομιστής MCP που είναι ενεργοποιημένος εξ ορισμού",
|
||||
"settings.knowledge_base.recognition.tip": "Ο πράκτορας θα καλέσει τη δυνατότητα αναγνώρισης πρόθεσης του μεγάλου μοντέλου για να αποφασίσει αν χρειάζεται να κληθεί η βάση γνώσης για να απαντηθεί, και αυτή η λειτουργία θα εξαρτηθεί από τις δυνατότητες του μοντέλου",
|
||||
"settings.knowledge_base.recognition": "Κλήση βάσης γνώσης",
|
||||
"settings.knowledge_base.recognition.off": "Υποχρεωτική αναζήτηση",
|
||||
"settings.knowledge_base.recognition.on": "Αναγνώριση πρόθεσης",
|
||||
"settings.reasoning_effort.default": "Προεπιλογή",
|
||||
"settings.more": "Ρυθμίσεις Βοηθού"
|
||||
},
|
||||
"auth": {
|
||||
"error": "Αποτυχία στην αυτόματη πήγαινη των κλειδιών, παρακαλείστε να το κάνετε χειροκίνητα",
|
||||
@@ -133,7 +175,8 @@
|
||||
"prev": "Προηγούμενο μήνυμα",
|
||||
"top": "Επιστροφή στην κορυφή",
|
||||
"bottom": "Επιστροφή στο κάτω μέρος",
|
||||
"close": "Κλείσιμο"
|
||||
"close": "Κλείσιμο",
|
||||
"history": "Ιστορικό συνομιλίας"
|
||||
},
|
||||
"resend": "Ξαναστείλε",
|
||||
"save": "Αποθήκευση",
|
||||
@@ -176,7 +219,6 @@
|
||||
"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": "Νέο (επιστροφή σε επιστροφή)",
|
||||
@@ -203,7 +245,54 @@
|
||||
"topics.unpinned": "Αποστέλλω",
|
||||
"translate": "Μετάφραση",
|
||||
"input.generate_image": "Δημιουργία εικόνας",
|
||||
"input.generate_image_not_supported": "Το μοντέλο δεν υποστηρίζει τη δημιουργία εικόνων"
|
||||
"input.generate_image_not_supported": "Το μοντέλο δεν υποστηρίζει τη δημιουργία εικόνων",
|
||||
"history": {
|
||||
"assistant_node": "Βοηθός",
|
||||
"click_to_navigate": "Κάντε κλικ για να μεταβείτε στο αντίστοιχο μήνυμα",
|
||||
"coming_soon": "Το διάγραμμα ροής συνομιλίας θα είναι σύντομα διαθέσιμο",
|
||||
"no_messages": "Δεν βρέθηκαν μηνύματα",
|
||||
"start_conversation": "Ξεκινήστε μια συνομιλία για να δείτε το διάγραμμα ροής",
|
||||
"title": "Ιστορικό συνομιλιών",
|
||||
"user_node": "Χρήστης",
|
||||
"view_full_content": "Προβολή πλήρους περιεχομένου"
|
||||
},
|
||||
"input.translating": "Μετάφραση...",
|
||||
"input.thinking": "Σκέψη",
|
||||
"input.thinking.mode.default": "Προεπιλογή",
|
||||
"input.thinking.mode.default.tip": "Το μοντέλο θα αποφασίσει αυτόματα τον αριθμό token για σκέψη",
|
||||
"input.thinking.mode.custom": "Προσαρμοσμένο",
|
||||
"input.thinking.mode.custom.tip": "Ο μέγιστος αριθμός token που μπορεί να σκεφτεί το μοντέλο. Πρέπει να ληφθεί υπόψη το όριο πλαισίου του μοντέλου, διαφορετικά θα εμφανιστεί σφάλμα",
|
||||
"input.thinking.budget_exceeds_max": "Ο προϋπολογισμός σκέψης υπερβαίνει τον μέγιστο αριθμό token",
|
||||
"input.upload.upload_from_local": "Μεταφόρτωση αρχείου από τον υπολογιστή...",
|
||||
"input.web_search.builtin": "Ενσωματωμένη στο μοντέλο",
|
||||
"input.web_search.builtin.enabled_content": "Χρήση της ενσωματωμένης δυνατότητας διαδικτυακής αναζήτησης του μοντέλου",
|
||||
"input.web_search.builtin.disabled_content": "Η τρέχουσα έκδοση του μοντέλου δεν υποστηρίζει τη δυνατότητα διαδικτυακής αναζήτησης",
|
||||
"input.web_search.no_web_search": "Χωρίς διαδίκτυο",
|
||||
"input.web_search.no_web_search.description": "Να μην ενεργοποιηθεί η δυνατότητα διαδικτυακής αναζήτησης",
|
||||
"settings.code_cacheable": "Κρυφή μνήμη κώδικα",
|
||||
"settings.code_cacheable.tip": "Η κρυφή μνήμη των τμημάτων κώδικα μπορεί να μειώσει τον χρόνο απεικόνισης μεγάλων τμημάτων κώδικα, αλλά αυξάνει τη χρήση μνήμης",
|
||||
"settings.code_cache_max_size": "Όριο κρυφής μνήμης",
|
||||
"settings.code_cache_max_size.tip": "Μέγιστος αριθμός χαρακτήρων (σε χιλιάδες) που επιτρέπεται να αποθηκευτούν στην κρυφή μνήμη, υπολογίζεται με βάση τον κώδικα με χρώματα. Ο κώδικας με χρώματα είναι πολύ πιο μακρύς από τον καθαρό κείμενο.",
|
||||
"settings.code_cache_ttl": "Διάρκεια κρυφής μνήμης",
|
||||
"settings.code_cache_ttl.tip": "Χρόνος λήξης της κρυφής μνήμης (σε λεπτά)",
|
||||
"settings.code_cache_threshold": "Κατώφλι κρυφής μνήμης",
|
||||
"settings.code_cache_threshold.tip": "Ελάχιστο μήκος κώδικα (σε χιλιάδες χαρακτήρες) που επιτρέπεται να αποθηκευτεί στην κρυφή μνήμη. Μόνο τα τμήματα που υπερβαίνουν το κατώφλι θα αποθηκεύονται στην κρυφή μνήμη",
|
||||
"topics.export.md.reason": "Εξαγωγή σε Markdown (περιλαμβανομένης της σκέψης)",
|
||||
"topics.export.obsidian_vault": "Αποθήκη Obsidian",
|
||||
"topics.export.obsidian_vault_placeholder": "Επιλέξτε το όνομα της αποθήκης",
|
||||
"topics.export.obsidian_path": "Διαδρομή",
|
||||
"topics.export.obsidian_path_placeholder": "Επιλέξτε διαδρομή",
|
||||
"topics.export.obsidian_no_vaults": "Δεν βρέθηκε αποθήκη Obsidian",
|
||||
"topics.export.obsidian_loading": "Φόρτωση...",
|
||||
"topics.export.obsidian_fetch_error": "Αποτυχία λήψης της αποθήκης Obsidian",
|
||||
"topics.export.obsidian_fetch_folders_error": "Αποτυχία λήψης της δομής φακέλων",
|
||||
"topics.export.obsidian_no_vault_selected": "Παρακαλώ επιλέξτε μια αποθήκη πρώτα",
|
||||
"topics.export.obsidian_select_vault_first": "Παρακαλώ επιλέξτε πρώτα μια αποθήκη",
|
||||
"topics.export.obsidian_root_directory": "Κυρίως κατάλογος",
|
||||
"topics.export.siyuan": "Εξαγωγή στο Siyuan Notepad",
|
||||
"topics.export.wait_for_title_naming": "Γενικευμένος τίτλος...",
|
||||
"topics.export.title_naming_success": "Ο τίτλος δημιουργήθηκε επιτυχώς",
|
||||
"topics.export.title_naming_failed": "Η δημιουργία του τίτλου απέτυχε, θα χρησιμοποιηθεί ο προεπιλεγμένος τίτλος"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "συμπεριληφθείς",
|
||||
@@ -254,7 +343,16 @@
|
||||
"select": "Επιλογή",
|
||||
"topics": "Θέματα",
|
||||
"warning": "Προσοχή",
|
||||
"you": "Εσείς"
|
||||
"you": "Εσείς",
|
||||
"sort": {
|
||||
"pinyin": "Ταξινόμηση κατά Πινγίν",
|
||||
"pinyin.asc": "Αύξουσα ταξινόμηση κατά Πινγίν",
|
||||
"pinyin.desc": "Φθίνουσα ταξινόμηση κατά Πινγίν"
|
||||
},
|
||||
"inspect": "Επιθεώρηση",
|
||||
"collapse": "Σύμπτυξη",
|
||||
"loading": "Φόρτωση...",
|
||||
"reasoning_content": "Έχει σκεφτεί πολύ καλά"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Βοήθεια"
|
||||
@@ -280,7 +378,9 @@
|
||||
"description": "Απέτυχε η ώθηση της εξίσωσης, παρακαλώ ελέγξτε το σωστό μορφάτι της",
|
||||
"title": "Σφάλμα Παρασκήνιου"
|
||||
},
|
||||
"user_message_not_found": "Αδυναμία εύρεσης της αρχικής μηνύματος χρήστη"
|
||||
"user_message_not_found": "Αδυναμία εύρεσης της αρχικής μηνύματος χρήστη",
|
||||
"unknown": "Άγνωστο σφάλμα",
|
||||
"pause_placeholder": "Διακόπηκε"
|
||||
},
|
||||
"export": {
|
||||
"assistant": "βοηθός",
|
||||
@@ -393,7 +493,11 @@
|
||||
"topN_tooltip": "Η ποσότητα των επιστρεφόμενων αποτελεσμάτων που συνάφονται, όσο μεγαλύτερη είναι η τιμή, τόσο περισσότερα αποτελέσματα συνδέονται, αλλά και οι καταναλωτικοί Token επειδή περισσότερα",
|
||||
"url_added": "Η διεύθυνση προστέθηκε",
|
||||
"url_placeholder": "Εισάγετε τη διεύθυνση, χωρίστε πολλαπλές διευθύνσεις με επιστροφή",
|
||||
"urls": "Διευθύνσεις"
|
||||
"urls": "Διευθύνσεις",
|
||||
"dimensions": "Διαστάσεις ενσωμάτωσης",
|
||||
"dimensions_size_tooltip": "Το μέγεθος των διαστάσεων ενσωμάτωσης. Όσο μεγαλύτερη η τιμή, τόσο περισσότερες οι διαστάσεις ενσωμάτωσης, αλλά και οι απαιτούμενες μονάδες (Tokens).",
|
||||
"dimensions_size_placeholder": "Προεπιλεγμένη τιμή (δεν συνιστάται να τροποποιηθεί)",
|
||||
"dimensions_size_too_large": "Οι διαστάσεις ενσωμάτωσης δεν μπορούν να υπερβούν το όριο περιεχομένου του μοντέλου ({{max_context}})"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Αραβικά",
|
||||
@@ -505,20 +609,63 @@
|
||||
"completed": "Ολοκληρώθηκε",
|
||||
"invoking": "κλήση σε εξέλιξη",
|
||||
"raw": "Ακατέργαστο",
|
||||
"preview": "Προεπισκόπηση"
|
||||
"preview": "Προεπισκόπηση",
|
||||
"error": "Προέκυψε σφάλμα"
|
||||
},
|
||||
"topic.added": "Η θεματική προστέθηκε επιτυχώς",
|
||||
"upgrade.success.button": "Επανεκκίνηση",
|
||||
"upgrade.success.content": "Επανεκκίνηση για να ολοκληρώσετε την ενημέρωση",
|
||||
"upgrade.success.title": "Η ενημέρωση ήταν επιτυχής",
|
||||
"warn.notion.exporting": "Εξαγωγή στο Notion, μην επαναλάβετε την διαδικασία εξαγωγής!",
|
||||
"warning.rate.limit": "Υπερβολική συχνότητα στείλατε παρακαλώ περιμένετε {{seconds}} δευτερόλεπτα και προσπαθήστε ξανά"
|
||||
"warning.rate.limit": "Υπερβολική συχνότητα στείλατε παρακαλώ περιμένετε {{seconds}} δευτερόλεπτα και προσπαθήστε ξανά",
|
||||
"agents": {
|
||||
"imported": "Εισήχθη επιτυχώς",
|
||||
"import.error": "Η εισαγωγή απέτυχε"
|
||||
},
|
||||
"citation": "{{count}} αναφορές",
|
||||
"error.invalid.nutstore": "Μη έγκυρη ρύθμιση Nutstore",
|
||||
"error.invalid.nutstore_token": "Μη έγκυρο Token Nutstore",
|
||||
"processing": "Επεξεργασία...",
|
||||
"error.siyuan.export": "Η έκθεση σημειώσεων Siyuan απέτυχε, ελέγξτε την κατάσταση σύνδεσης και τις ρυθμίσεις σύμφωνα με τα έγγραφα",
|
||||
"error.siyuan.no_config": "Δεν έχει ρυθμιστεί η διεύθυνση API ή το Token του Siyuan Notes",
|
||||
"success.siyuan.export": "Επιτυχής εξαγωγή στις σημειώσεις Siyuan",
|
||||
"warn.yuque.exporting": "Γίνεται έκθεση Yuque· μην ξαναζητήσετε την έκθεση!",
|
||||
"warn.siyuan.exporting": "Γίνεται εξαγωγή στις σημειώσεις Siyuan· μην ξαναζητήσετε την έκθεση!",
|
||||
"download.success": "Λήψη ολοκληρώθηκε",
|
||||
"download.failed": "Αποτυχία λήψης"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Προσθήκη στην πλευρή",
|
||||
"sidebar.remove.title": "Αφαίρεση από την πλευρή",
|
||||
"sidebar.hide.title": "Απόκρυψη μικροπρογράμματος",
|
||||
"title": "Μικρόπρογραμμα"
|
||||
"title": "Μικρόπρογραμμα",
|
||||
"popup": {
|
||||
"refresh": "Ανανέωση",
|
||||
"close": "Κλείσιμο της εφαρμογής",
|
||||
"minimize": "Ελαχιστοποίηση της εφαρμογής",
|
||||
"devtools": "Εργαλεία προγραμματιστή",
|
||||
"openExternal": "Άνοιγμα στον περιηγητή",
|
||||
"rightclick_copyurl": "Αντιγραφή URL με δεξί κλικ",
|
||||
"open_link_external_on": "Τρέχον: Άνοιγμα συνδέσμου στον περιηγητή",
|
||||
"open_link_external_off": "Τρέχον: Άνοιγμα συνδέσμου χρησιμοποιώντας το προεπιλεγμένο παράθυρο"
|
||||
},
|
||||
"sidebar": {
|
||||
"add": {
|
||||
"title": "Προσθήκη στην πλευρική μπάρα"
|
||||
},
|
||||
"remove": {
|
||||
"title": "Αφαίρεση από την πλευρική μπάρα"
|
||||
},
|
||||
"remove_custom": {
|
||||
"title": "Διαγραφή προσαρμοσμένης εφαρμογής"
|
||||
},
|
||||
"hide": {
|
||||
"title": "Απόκρυψη"
|
||||
},
|
||||
"close": {
|
||||
"title": "Κλείσιμο"
|
||||
},
|
||||
"closeall": {
|
||||
"title": "Κλείσιμο όλων"
|
||||
}
|
||||
}
|
||||
},
|
||||
"miniwindow": {
|
||||
"clipboard": {
|
||||
@@ -534,13 +681,17 @@
|
||||
"copy_last_message": "Παράκαμε το τελευταίο μήνυμα",
|
||||
"esc": "πατήστε ESC για {{action}}",
|
||||
"esc_back": "Επιστροφή",
|
||||
"esc_close": "Κλείσιμο παραθύρου"
|
||||
"esc_close": "Κλείσιμο παραθύρου",
|
||||
"backspace_clear": "Πατήστε το πλήκτρο Backspace για να κάνετε εκκαθάριση"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": {
|
||||
"empty": "Ρώτα τον {{model}} για βοήθεια...",
|
||||
"title": "Τι θέλεις να κάνεις με το κείμενο που είναι παρακάτω"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"pin": "Καρφίτσωμα παραθύρου"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
@@ -577,7 +728,9 @@
|
||||
"free": "δωρεάν",
|
||||
"rerank": "Τακτοποιώ",
|
||||
"websearch": "δικτύωση"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "Ο επαναξιολογητικός μοντέλος δεν υποστηρίζει αυτόν τον πάροχο ({{provider}})",
|
||||
"enable_tool_use": "Ενεργοποίηση κλήσης εργαλείου"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Επισκευή διαλόγου",
|
||||
@@ -609,7 +762,59 @@
|
||||
"regenerate.confirm": "Αυτό θα επιβάλει τις δημιουργίες που έχετε κάνει, θέλετε να συνεχίσετε;",
|
||||
"seed": "Τυχαίος παράγοντας",
|
||||
"seed_tip": "Η χρήση του ίδιου παραγόντα και του σχολίου μπορεί να δημιουργήσει παρόμοιες εικόνες",
|
||||
"title": "Εικόνα"
|
||||
"title": "Εικόνα",
|
||||
"mode": {
|
||||
"generate": "Δημιουργία",
|
||||
"edit": "Επεξεργασία",
|
||||
"remix": "Ανάμειξη",
|
||||
"upscale": "Μεγέθυνση"
|
||||
},
|
||||
"generate": {
|
||||
"model_tip": "Έκδοση μοντέλου: Το V2 είναι το τελευταίο μοντέλο διεπαφής, το V2A είναι γρήγορο μοντέλο, το V_1 είναι το αρχικό μοντέλο και το _TURBO είναι η επιταχυνόμενη έκδοση",
|
||||
"number_images_tip": "Αριθμός εικόνων ανά παραγωγή",
|
||||
"seed_tip": "Ελέγχει την τυχαιότητα της δημιουργίας εικόνας, χρησιμοποιείται για να επαναληφθεί το ίδιο αποτέλεσμα",
|
||||
"negative_prompt_tip": "Περιγράψτε στοιχεία που δεν θέλετε να εμφανίζονται στην εικόνα, υποστηρίζεται μόνο στις εκδόσεις V_1, V_1_TURBO, V_2 και V_2_TURBO",
|
||||
"magic_prompt_option_tip": "Έξυπνη βελτιστοποίηση της προτροπής για βελτίωση των αποτελεσμάτων",
|
||||
"style_type_tip": "Στυλ δημιουργίας εικόνας, ισχύει μόνο για την έκδοση V_2 και μεταγενέστερες"
|
||||
},
|
||||
"edit": {
|
||||
"image_file": "Επεξεργασμένη εικόνα",
|
||||
"model_tip": "Η λειτουργία επεξεργασίας υποστηρίζεται μόνο από τις εκδόσεις V_2 και V_2_TURBO",
|
||||
"number_images_tip": "Αριθμός των αποτελεσμάτων επεξεργασίας που θα δημιουργηθούν",
|
||||
"style_type_tip": "Ο τύπος στυλ για την επεξεργασμένη εικόνα, ισχύει μόνο για την έκδοση V_2 και νεότερες",
|
||||
"seed_tip": "Έλεγχος της τυχαιότητας στα αποτελέσματα επεξεργασίας",
|
||||
"magic_prompt_option_tip": "Έξυπνη βελτιστοποίηση της πρότασης επεξεργασίας"
|
||||
},
|
||||
"remix": {
|
||||
"model_tip": "Επιλέξτε την έκδοση του AI μοντέλου για χρήση σε remix",
|
||||
"image_file": "Εικόνα αναφοράς",
|
||||
"image_weight": "Βάρος εικόνας αναφοράς",
|
||||
"image_weight_tip": "Ρυθμίστε την επίδραση της εικόνας αναφοράς",
|
||||
"number_images_tip": "Αριθμός αποτελεσμάτων remix που θα δημιουργηθούν",
|
||||
"seed_tip": "Έλεγχος τυχαιότητας των αποτελεσμάτων remix",
|
||||
"style_type_tip": "Στυλ εικόνας μετά το remix, διαθέσιμο μόνο για εκδόσεις V_2 και νεότερες",
|
||||
"negative_prompt_tip": "Περιγράψτε στοιχεία που δεν θέλετε να εμφανιστούν στο αποτέλεσμα remix",
|
||||
"magic_prompt_option_tip": "Έξυπνη βελτιστοποίηση της προτροπής remix"
|
||||
},
|
||||
"upscale": {
|
||||
"image_file": "Εικόνα που χρειάζεται μεγέθυνση",
|
||||
"resemblance": "Ομοιότητα",
|
||||
"resemblance_tip": "Ρυθμίστε την ομοιότητα της μεγεθυσμένης εικόνας με την αρχική",
|
||||
"detail": "Λεπτομέρεια",
|
||||
"detail_tip": "Ρυθμίστε την ένταση των λεπτομερειών στην μεγεθυσμένη εικόνα",
|
||||
"number_images_tip": "Αριθμός των αποτελεσμάτων μεγέθυνσης που θα δημιουργηθούν",
|
||||
"seed_tip": "Ελέγχει την τυχαιότητα του αποτελέσματος μεγέθυνσης",
|
||||
"magic_prompt_option_tip": "Έξυπνη βελτιστοποίηση της προτροπής μεγέθυνσης"
|
||||
},
|
||||
"magic_prompt_option": "Ενίσχυση προτροπής",
|
||||
"model": "Έκδοση",
|
||||
"aspect_ratio": "Λόγος διαστάσεων",
|
||||
"style_type": "Στυλ",
|
||||
"learn_more": "Μάθετε περισσότερα",
|
||||
"prompt_placeholder_edit": "Εισάγετε την περιγραφή της εικόνας σας, χρησιμοποιήστε διπλά εισαγωγικά \"\" για κείμενο",
|
||||
"proxy_required": "Αυτή τη στιγμή χρειάζεται να ενεργοποιήσετε τον μεσολαβητή (proxy) για να δείτε τις δημιουργημένες εικόνες. Στο μέλλον θα υποστηρίζεται η άμεση σύνδεση στην Κίνα",
|
||||
"image_file_required": "Παρακαλώ ανεβάστε πρώτα μια εικόνα",
|
||||
"image_file_retry": "Παρακαλώ ανεβάστε ξανά την εικόνα"
|
||||
},
|
||||
"plantuml": {
|
||||
"download": {
|
||||
@@ -672,7 +877,9 @@
|
||||
"xirang": "China Telecom Xiran",
|
||||
"yi": "Zero One Wanyiwu",
|
||||
"zhinao": "360 Intelligent Brain",
|
||||
"zhipu": "Zhipu AI"
|
||||
"zhipu": "Zhipu AI",
|
||||
"voyageai": "Voyage AI",
|
||||
"qiniu": "Qiniu Cloud"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "Είστε σίγουροι ότι θέλετε να επαναφέρετε τα δεδομένα;",
|
||||
@@ -714,7 +921,6 @@
|
||||
"advanced.title": "Ρυθμίσεις Ανώτερου Νiveau",
|
||||
"assistant": "Πρόεδρος Υπηρεσίας",
|
||||
"assistant.model_params": "Παράμετροι Μοντέλου",
|
||||
"assistant.show.icon": "Εμφάνιση εικονιδίου μοντέλου",
|
||||
"assistant.title": "Πρόεδρος Υπηρεσίας",
|
||||
"data": {
|
||||
"app_data": "Δεδομένα εφαρμογής",
|
||||
@@ -780,13 +986,13 @@
|
||||
"notion.split_size_placeholder": "Εισαγάγετε τον περιορισμό μπλοκ κάθε σελίδας (προεπιλογή: 90)",
|
||||
"notion.title": "Ρυθμίσεις του Notion",
|
||||
"obsidian": {
|
||||
"folder": "Φάκελος",
|
||||
"folder_placeholder": "Παρακαλώ εισάγετε όνομα φακέλου",
|
||||
"tags": "Παγκόσμιοι ετικέτες",
|
||||
"tags_placeholder": "Παρακαλώ εισάγετε ονόματα ετικετών, χωριστά με κόμματα, οι ετικέτες στο Obsidian δεν μπορούν να είναι μόνο αριθμοί",
|
||||
"title": "Ρύθμιση του Obsidian",
|
||||
"vault": "Αποθήκη",
|
||||
"vault_placeholder": "Παρακαλώ εισάγετε όνομα αποθήκης"
|
||||
"default_vault": "Προεπιλεγμένο αποθετήριο Obsidian",
|
||||
"default_vault_placeholder": "Επιλέξτε προεπιλεγμένο αποθετήριο Obsidian",
|
||||
"default_vault_loading": "Ανάκτηση αποθετηρίου Obsidian...",
|
||||
"default_vault_no_vaults": "Δεν βρέθηκε αποθετήριο Obsidian",
|
||||
"default_vault_fetch_error": "Αποτυχία ανάκτησης αποθετηρίου Obsidian",
|
||||
"default_vault_export_failed": "Η εξαγωγή απέτυχε"
|
||||
},
|
||||
"title": "Ρυθμίσεις δεδομένων",
|
||||
"webdav": {
|
||||
@@ -818,7 +1024,26 @@
|
||||
"title": "WebDAV",
|
||||
"user": "Όνομα χρήστη WebDAV",
|
||||
"maxBackups": "Μέγιστο αριθμό αρχείων αντιγραφής ασφαλείας",
|
||||
"maxBackups.unlimited": "Απεριόριστο"
|
||||
"maxBackups.unlimited": "Απεριόριστο",
|
||||
"backup.manager.title": "Διαχείριση δεδομένων αντιγράφου ασφαλείας",
|
||||
"backup.manager.refresh": "Ανανέωση",
|
||||
"backup.manager.delete.selected": "Διαγραφή επιλεγμένων",
|
||||
"backup.manager.delete.text": "Διαγραφή",
|
||||
"backup.manager.restore.text": "Επαναφορά",
|
||||
"backup.manager.restore.success": "Η επαναφορά ήταν επιτυχής, η εφαρμογή θα ανανεωθεί σε λίγα δευτερόλεπτα",
|
||||
"backup.manager.restore.error": "Αποτυχία επαναφοράς",
|
||||
"backup.manager.delete.confirm.title": "Επιβεβαίωση διαγραφής",
|
||||
"backup.manager.delete.confirm.single": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αντίγραφο ασφαλείας \"{{fileName}}\"; Η ενέργεια αυτή δεν μπορεί να αναιρεθεί.",
|
||||
"backup.manager.delete.confirm.multiple": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τα {{count}} επιλεγμένα αντίγραφα ασφαλείας; Η ενέργεια αυτή δεν μπορεί να αναιρεθεί.",
|
||||
"backup.manager.delete.success.single": "Η διαγραφή ήταν επιτυχής",
|
||||
"backup.manager.delete.success.multiple": "Τα {{count}} αντίγραφα ασφαλείας διαγράφηκαν επιτυχώς",
|
||||
"backup.manager.delete.error": "Αποτυχία διαγραφής",
|
||||
"backup.manager.fetch.error": "Αποτυχία λήψης αντιγράφων ασφαλείας",
|
||||
"backup.manager.select.files.delete": "Παρακαλώ επιλέξτε τα αντίγραφα ασφαλείας προς διαγραφή",
|
||||
"backup.manager.columns.fileName": "Όνομα αρχείου",
|
||||
"backup.manager.columns.modifiedTime": "Ώρα τροποποίησης",
|
||||
"backup.manager.columns.size": "Μέγεθος",
|
||||
"backup.manager.columns.actions": "Ενέργειες"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -834,16 +1059,73 @@
|
||||
"title": "Ρύθμιση Yusi",
|
||||
"token": "Token του Yusi",
|
||||
"token_placeholder": "Παρακαλούμε εισάγετε το Token του Yusi"
|
||||
}
|
||||
},
|
||||
"export_menu": {
|
||||
"title": "Εξαγωγή ρυθμίσεων μενού",
|
||||
"image": "Εξαγωγή ως εικόνα",
|
||||
"markdown": "Εξαγωγή σε Markdown",
|
||||
"markdown_reason": "Εξαγωγή σε Markdown (περιλαμβάνει σκέψη)",
|
||||
"notion": "Εξαγωγή στο Notion",
|
||||
"yuque": "Εξαγωγή στο Yuque",
|
||||
"obsidian": "Εξαγωγή στο Obsidian",
|
||||
"siyuan": "Εξαγωγή στο Ση-Υάν",
|
||||
"joplin": "Εξαγωγή στο Joplin",
|
||||
"docx": "Εξαγωγή σε Word"
|
||||
},
|
||||
"siyuan": {
|
||||
"check": {
|
||||
"title": "Έλεγχος Σύνδεσης",
|
||||
"button": "Έλεγχος",
|
||||
"empty_config": "Παρακαλώ εισάγετε τη διεύθυνση API και το token",
|
||||
"success": "Η σύνδεση ήταν επιτυχής",
|
||||
"fail": "Αποτυχία σύνδεσης, παρακαλώ ελέγξτε τη διεύθυνση API και το token",
|
||||
"error": "Αιφνίδια διακοπή σύνδεσης, παρακαλώ ελέγξτε τη σύνδεση δικτύου"
|
||||
},
|
||||
"title": "Ρυθμίσεις του Siyuan Σημειώσεων",
|
||||
"api_url": "Διεύθυνση API",
|
||||
"api_url_placeholder": "Παράδειγμα: http://127.0.0.1:6806",
|
||||
"token": "Κλειδί API",
|
||||
"token.help": "Λήψη από Siyuan Σημειώσεις -> Ρυθμίσεις -> Σχετικά",
|
||||
"token_placeholder": "Εισάγετε το κλειδί των Siyuan Σημειώσεων",
|
||||
"box_id": "ID Υπολογιστή",
|
||||
"box_id_placeholder": "Εισάγετε το ID υπολογιστή",
|
||||
"root_path": "Κεντρική διαδρομή εγγράφων",
|
||||
"root_path_placeholder": "Παράδειγμα: /CherryStudio"
|
||||
},
|
||||
"nutstore": {
|
||||
"title": "Ρυθμίσεις Jotunn Cloud",
|
||||
"isLogin": "Συνδεδεμένος",
|
||||
"notLogin": "Μη συνδεδεμένος",
|
||||
"login.button": "Σύνδεση",
|
||||
"logout.button": "Αποσύνδεση",
|
||||
"logout.title": "Επιβεβαίωση αποσύνδεσης από το Jotunn Cloud;",
|
||||
"logout.content": "Μετά την αποσύνδεση δεν θα μπορείτε να κάνετε αντίγραφο ασφαλείας ή να ανακτήσετε δεδομένα από το Jotunn Cloud",
|
||||
"checkConnection.name": "Έλεγχος σύνδεσης",
|
||||
"checkConnection.success": "Συνδεδεμένο στο Jotunn Cloud",
|
||||
"checkConnection.fail": "Αποτυχία σύνδεσης στο Jotunn Cloud",
|
||||
"username": "Όνομα χρήστη Jotunn Cloud",
|
||||
"path": "Διαδρομή αποθήκευσης Jotunn Cloud",
|
||||
"path.placeholder": "Παρακαλώ εισάγετε τη διαδρομή αποθήκευσης του Jotunn Cloud",
|
||||
"backup.button": "Αντίγραφο ασφαλείας στο Jotunn Cloud",
|
||||
"restore.button": "Επαναφορά από το Jotunn Cloud",
|
||||
"pathSelector.title": "Διαδρομή αποθήκευσης Jotunn Cloud",
|
||||
"pathSelector.return": "Πίσω",
|
||||
"pathSelector.currentPath": "Τρέχουσα διαδρομή",
|
||||
"new_folder.button.confirm": "Επιβεβαίωση",
|
||||
"new_folder.button.cancel": "Άκυρο",
|
||||
"new_folder.button": "Νέος φάκελος"
|
||||
},
|
||||
"divider.basic": "Ρυθμίσεις βασικών δεδομένων",
|
||||
"divider.cloud_storage": "Ρυθμίσεις αποθήκευσης στο νέφος",
|
||||
"divider.export_settings": "Ρυθμίσεις εξαγωγής",
|
||||
"divider.third_party": "Σύνδεση τρίτων",
|
||||
"message_title.use_topic_naming.title": "Δημιουργία τίτλων μηνυμάτων χρησιμοποιώντας μοντέλο ονομασίας θεμάτων",
|
||||
"message_title.use_topic_naming.help": "Όταν είναι ενεργό, δημιουργεί τίτλους για τα μηνύματα που εξάγονται χρησιμοποιώντας μοντέλο ονομασίας θεμάτων. Αυτό επηρεάζει επίσης όλες τις μεθόδους εξαγωγής μέσω Markdown."
|
||||
},
|
||||
"display.assistant.title": "Ρυθμίσεις Υπηρεσίας",
|
||||
"display.custom.css": "Προσαρμοστική CSS",
|
||||
"display.custom.css.cherrycss": "Λήψη από cherrycss.com",
|
||||
"display.custom.css.placeholder": "/* Γράψτε εδώ την προσαρμοστική CSS */",
|
||||
"display.minApp.disabled": "Αποκρυμμένα μικροπρογράμματα",
|
||||
"display.minApp.empty": "Βάλτε εδώ τα μικροπρογράμματα που θέλετε να κρύψετε από την αριστερά",
|
||||
"display.minApp.title": "Ρυθμίσεις εμφάνισης μικροπρογραμμάτων",
|
||||
"display.minApp.visible": "Εμφανιζόμενα μικροπρογράμματα",
|
||||
"display.sidebar.chat.hiddenMessage": "Η υπηρεσία είναι βασική λειτουργία και δεν υποστηρίζεται η κρυμμένη εμφάνιση",
|
||||
"display.sidebar.disabled": "Αποκρυμμένα εικονίδια",
|
||||
"display.sidebar.empty": "Βάλτε εδώ τις λειτουργίες που θέλετε να κρύψετε από την αριστερά",
|
||||
@@ -864,7 +1146,6 @@
|
||||
"general.display.title": "Ρυθμίσεις εμφάνισης",
|
||||
"general.emoji_picker": "Επιλογή σμιλιών",
|
||||
"general.image_upload": "Φόρτωση εικόνων",
|
||||
"general.manually_check_update.title": "Απενεργοποίηση ελέγχου ενημερώσεων",
|
||||
"general.reset.button": "Επαναφορά",
|
||||
"general.reset.title": "Επαναφορά δεδομένων",
|
||||
"general.restore.button": "Αποκατάσταση",
|
||||
@@ -930,11 +1211,94 @@
|
||||
"serverPlural": "Διακομιστές",
|
||||
"serverSingular": "Διακομιστής",
|
||||
"title": "Διακομιστές MCP",
|
||||
"toggleError": "Αποτυχία εναλλαγής",
|
||||
"type": "Τύπος",
|
||||
"updateError": "Αποτυχία ενημέρωσης διακομιστή",
|
||||
"updateSuccess": "Ο διακομιστής ενημερώθηκε επιτυχώς",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"errors": {
|
||||
"32000": "Η εκκίνηση του MCP απέτυχε. Παρακαλώ ελέγξτε αν όλες οι παράμετροι έχουν συμπληρωθεί σύμφωνα με τον οδηγό."
|
||||
},
|
||||
"tabs": {
|
||||
"general": "Γενικά",
|
||||
"description": "Περιγραφή",
|
||||
"tools": "Εργαλεία",
|
||||
"prompts": "Ερωτήματα",
|
||||
"resources": "Πόροι"
|
||||
},
|
||||
"tools": {
|
||||
"inputSchema": "Είσοδος Σχήματος",
|
||||
"availableTools": "Διαθέσιμα Εργαλεία",
|
||||
"noToolsAvailable": "Δεν υπάρχουν διαθέσιμα εργαλεία",
|
||||
"loadError": "Αποτυχία φόρτωσης εργαλείων"
|
||||
},
|
||||
"prompts": {
|
||||
"availablePrompts": "Διαθέσιμες Υποδείξεις",
|
||||
"noPromptsAvailable": "Δεν υπάρχουν διαθέσιμες υποδείξεις",
|
||||
"arguments": "Ορίσματα",
|
||||
"requiredField": "Υποχρεωτικό πεδίο",
|
||||
"genericError": "Σφάλμα κατά τη λήψη της υπόδειξης",
|
||||
"loadError": "Αποτυχία λήψης υπόδειξης"
|
||||
},
|
||||
"resources": {
|
||||
"noResourcesAvailable": "Δεν υπάρχουν διαθέσιμοι πόροι",
|
||||
"availableResources": "Διαθέσιμοι πόροι",
|
||||
"uri": "URI",
|
||||
"mimeType": "Τύπος MIME",
|
||||
"size": "Μέγεθος",
|
||||
"blob": "Δυαδικά δεδομένα",
|
||||
"blobInvisible": "Αόρατα δυαδικά δεδομένα",
|
||||
"text": "Κείμενο"
|
||||
},
|
||||
"types": {
|
||||
"inMemory": "Ενσωματωμένη",
|
||||
"sse": "SSE",
|
||||
"streamableHttp": "Ροή",
|
||||
"stdio": "STDIO"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Συγχρονισμός Διακομιστή",
|
||||
"selectProvider": "Επιλέξτε Πάροχο:",
|
||||
"discoverMcpServers": "Ανακάλυψη MCP Διακομιστών",
|
||||
"discoverMcpServersDescription": "Πρόσβαση στην πλατφόρμα για ανακάλυψη διαθέσιμων MCP διακομιστών",
|
||||
"getToken": "Λήψη API Τοκεν",
|
||||
"getTokenDescription": "Λήψη ενός προσωπικού API τοκεν από τον λογαριασμό σας",
|
||||
"setToken": "Εισαγάγετε το τοκεν σας",
|
||||
"tokenRequired": "Απαιτείται API Τοκεν",
|
||||
"tokenPlaceholder": "Εισάγετε το API τοκεν εδώ",
|
||||
"button": "Συγχρονισμός",
|
||||
"error": "Σφάλμα κατά τον συγχρονισμό MCP διακομιστή",
|
||||
"success": "Ο συγχρονισμός MCP διακομιστή ολοκληρώθηκε επιτυχώς",
|
||||
"unauthorized": "Δεν εξουσιοδοτήθηκε ο συγχρονισμός",
|
||||
"noServersAvailable": "Δεν υπάρχουν διαθέσιμοι MCP διακομιστές"
|
||||
},
|
||||
"sse": "Συμβάντα Αποστολής από τον Διακομιστή (sse)",
|
||||
"streamableHttp": "Ρέουσα μεταφορά HTTP (streamableHttp)",
|
||||
"stdio": "Πρότυπη Είσοδος/Έξοδος (stdio)",
|
||||
"inMemory": "Σε Μνήμη",
|
||||
"headers": "Κεφαλίδες",
|
||||
"headersTooltip": "Προσαρμοσμένες κεφαλίδες HTTP αιτήσεων",
|
||||
"searchNpx": "Αναζήτηση MCP",
|
||||
"newServer": "Διακομιστής MCP",
|
||||
"startError": "Εκκίνηση Απέτυχε",
|
||||
"editMcpJson": "Επεξεργασία ρύθμισης MCP",
|
||||
"installHelp": "Λήψη βοήθειας εγκατάστασης",
|
||||
"deleteServer": "Διαγραφή διακομιστή",
|
||||
"deleteServerConfirm": "Είστε σίγουρος ότι θέλετε να διαγράψετε αυτόν τον διακομιστή;",
|
||||
"registry": "Πηγή Διαχείρισης πακέτων",
|
||||
"registryTooltip": "Επιλέξτε την πηγή για την εγκατάσταση πακέτων, για να αντιμετωπιστούν προβλήματα δικτύου από την προεπιλεγμένη πηγή.",
|
||||
"registryDefault": "Προεπιλεγμένη",
|
||||
"not_support": "Το μοντέλο δεν υποστηρίζεται",
|
||||
"user": "Χρήστης",
|
||||
"system": "Σύστημα",
|
||||
"timeout": "Τερματισμός λόγω αδράνειας",
|
||||
"timeoutTooltip": "Ο χρόνος λήξης αιτήσεων για αυτόν τον διακομιστή (σε δευτερόλεπτα), προεπιλεγμένος είναι 60 δευτερόλεπτα",
|
||||
"provider": "Πάροχος",
|
||||
"providerUrl": "URL Παρόχου",
|
||||
"logoUrl": "URL Λογότυπου",
|
||||
"tags": "Ετικέτες",
|
||||
"tagsPlaceholder": "Εισάγετε ετικέτες",
|
||||
"providerPlaceholder": "Όνομα παρόχου",
|
||||
"advancedSettings": "Προχωρημένες Ρυθμίσεις"
|
||||
},
|
||||
"messages.divider": "Διαχωριστική γραμμή μηνυμάτων",
|
||||
"messages.grid_columns": "Αριθμός στήλων γριλ μηνυμάτων",
|
||||
@@ -1046,13 +1410,29 @@
|
||||
"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": "Υπηρεσία μονάδων"
|
||||
"title": "Υπηρεσία μονάδων",
|
||||
"oauth": {
|
||||
"button": "Σύνδεση με λογαριασμό {{provider}}",
|
||||
"description": "Η υπηρεσία παρέχεται από την ιστοσελίδα {{provider}}",
|
||||
"official_website": "Επίσημη ιστοσελίδα"
|
||||
},
|
||||
"notes": {
|
||||
"title": "Σχόλιο Μοντέλου",
|
||||
"placeholder": "Εισάγετε περιεχόμενο σε μορφή Markdown...",
|
||||
"markdown_editor_default_value": "Περιοχή Προεπισκόπησης"
|
||||
},
|
||||
"basic_auth": "Πιστοποίηση HTTP",
|
||||
"basic_auth.tip": "Ισχύει για περιπτώσεις που τοποθετούνται σε διακομιστή (δείτε την τεκμηρίωση). Υποστηρίζεται μόνο το σχήμα Basic (RFC7617).",
|
||||
"basic_auth.user_name": "Όνομα χρήστη",
|
||||
"basic_auth.user_name.tip": "Αφήστε κενό για να απενεργοποιήσετε",
|
||||
"basic_auth.password": "Κωδικός πρόσβασης",
|
||||
"bills": "Λογαριασμοί",
|
||||
"no_models_for_check": "Δεν υπάρχουν μοντέλα για έλεγχο (π.χ. μοντέλα συνομιλίας)"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1129,8 +1509,111 @@
|
||||
"description": "Το Tavily είναι ένα αναζητητής που διαμορφώνεται για AI-agents, παρέχοντας συνεχεία ακριβείς αποτελέσματα, νοηματικές προτάσεις αναζήτησης και βαθειά ικανότητες μελέτης",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Διαδικτυακή αναζήτηση"
|
||||
}
|
||||
"title": "Διαδικτυακή αναζήτηση",
|
||||
"overwrite": "Επικάλυψη πάροχου αναζήτησης",
|
||||
"overwrite_tooltip": "Εξαναγκαστική χρήση του πάροχου αναζήτησης αντί του μεγάλου γλωσσικού μοντέλου για αναζήτηση",
|
||||
"subscribe": "Συνδρομή λίστας αποκλεισμού",
|
||||
"subscribe_update": "Ενημέρωση τώρα",
|
||||
"subscribe_add": "Προσθήκη συνδρομής",
|
||||
"subscribe_url": "Διεύθυνση συνδρομής",
|
||||
"subscribe_name": "Εναλλακτικό όνομα",
|
||||
"subscribe_name.placeholder": "Εναλλακτικό όνομα που θα χρησιμοποιείται όταν η ληφθείσα συνδρομή δεν έχει όνομα",
|
||||
"subscribe_add_success": "Η συνδρομή προστέθηκε επιτυχώς!",
|
||||
"subscribe_delete": "Διαγραφή συνδρομής",
|
||||
"apikey": "Κλειδί API",
|
||||
"free": "Δωρεάν",
|
||||
"content_limit": "Περιορισμός μήκους περιεχομένου",
|
||||
"content_limit_tooltip": "Περιορίζει το μήκος του περιεχομένου των αποτελεσμάτων αναζήτησης, το περιεχόμενο πέρα από το όριο θα περικόπτεται"
|
||||
},
|
||||
"miniapps": {
|
||||
"open_link_external": {
|
||||
"title": "Άνοιγμα νέου παραθύρου σύνδεσης στον περιηγητή"
|
||||
},
|
||||
"custom": {
|
||||
"title": "Προσαρμοσμένη Εφαρμογή",
|
||||
"edit_title": "Επεξεργασία Προσαρμοσμένης Εφαρμογής",
|
||||
"save_success": "Η προσαρμοσμένη εφαρμογή αποθηκεύτηκε επιτυχώς.",
|
||||
"save_error": "Αποτυχία αποθήκευσης της προσαρμοσμένης εφαρμογής.",
|
||||
"remove_success": "Η προσαρμοσμένη εφαρμογή διαγράφηκε επιτυχώς.",
|
||||
"remove_error": "Αποτυχία διαγραφής της προσαρμοσμένης εφαρμογής.",
|
||||
"logo_upload_success": "Το Logo μεταφορτώθηκε επιτυχώς.",
|
||||
"logo_upload_error": "Αποτυχία μεταφόρτωσης του Logo.",
|
||||
"id": "ID",
|
||||
"id_error": "Το ID είναι υποχρεωτικό πεδίο.",
|
||||
"id_placeholder": "Παρακαλώ εισάγετε το ID",
|
||||
"name": "Όνομα",
|
||||
"name_error": "Το Όνομα είναι υποχρεωτικό πεδίο.",
|
||||
"name_placeholder": "Παρακαλώ εισάγετε το όνομα",
|
||||
"url": "URL",
|
||||
"url_error": "Το URL είναι υποχρεωτικό πεδίο.",
|
||||
"url_placeholder": "Παρακαλώ εισάγετε το URL",
|
||||
"logo": "Logo",
|
||||
"logo_url": "Logo URL",
|
||||
"logo_file": "Μεταφόρτωση Logo Αρχείου",
|
||||
"logo_url_label": "Logo URL",
|
||||
"logo_url_placeholder": "Παρακαλώ εισάγετε το Logo URL",
|
||||
"logo_upload_label": "Μεταφόρτωση Logo",
|
||||
"logo_upload_button": "Μεταφόρτωση",
|
||||
"save": "Αποθήκευση",
|
||||
"edit_description": "Επεξεργαστείτε τη διαμόρφωση της προσαρμοσμένης σας εφαρμογής εδώ. Κάθε εφαρμογή πρέπει να περιλαμβάνει τα πεδία id, name, url και logo.",
|
||||
"placeholder": "Παρακαλώ εισάγετε τη διαμόρφωση της προσαρμοσμένης εφαρμογής (Μορφή JSON)",
|
||||
"duplicate_ids": "Εντοπίστηκαν διπλότυπα ID: {{ids}}",
|
||||
"conflicting_ids": "Υπάρχει σύγκρουση με τα προεπιλεγμένα ID της εφαρμογής: {{ids}}"
|
||||
},
|
||||
"title": "Ρυθμίσεις Μικροπρογράμματος",
|
||||
"disabled": "Απόκρυψη μικροπρογράμματος",
|
||||
"empty": "Σύρετε το μικροπρόγραμμα που θέλετε να αποκρύψετε από την αριστερή πλευρά σε αυτήν την περιοχή",
|
||||
"visible": "Εμφανιζόμενα μικροπρογράμματα",
|
||||
"cache_settings": "Ρυθμίσεις Προσωρινής Μνήμης",
|
||||
"cache_title": "Ποσότητα Προσωρινής Μνήμης Μικροπρογράμματος",
|
||||
"cache_description": "Ορίστε τον μέγιστο αριθμό των μικροπρογραμμάτων που μπορούν να είναι ενεργά ταυτόχρονα",
|
||||
"reset_tooltip": "Επαναφορά στις προεπιλεγμένες τιμές",
|
||||
"display_title": "Ρυθμίσεις Εμφάνισης Μικροπρογράμματος",
|
||||
"sidebar_title": "Ρυθμίσεις Εμφάνισης Ενεργού Μικροπρογράμματος στην Πλευρική Γραμμή",
|
||||
"sidebar_description": "Καθορίστε εάν το ενεργό μικροπρόγραμμα θα εμφανίζεται στην πλευρική γραμμή",
|
||||
"cache_change_notice": "Η αλλαγή θα τεθεί σε ισχύ αφού το πλήθος των ανοιχτών μικροπρογραμμάτων φτάσει τη ρυθμισμένη τιμή"
|
||||
},
|
||||
"quickPhrase": {
|
||||
"title": "Γρήγορες Φράσεις",
|
||||
"add": "Προσθήκη Φράσης",
|
||||
"edit": "Επεξεργασία Φράσης",
|
||||
"titleLabel": "Τίτλος",
|
||||
"contentLabel": "Περιεχόμενο",
|
||||
"titlePlaceholder": "Παρακαλώ εισάγετε τίτλο φράσης",
|
||||
"contentPlaceholder": "Παρακαλώ εισάγετε περιεχόμενο φράσης. Υποστηρίζεται η χρήση μεταβλητών, και στη συνέχεια πατήστε Tab για να μεταβείτε γρήγορα στη μεταβλητή και να την τροποποιήσετε. Για παράδειγμα: \\\\n Βοήθησέ με να σχεδιάσω μια διαδρομή από το ${from} στο ${to}, και στη συνέχεια να τη στείλεις στο ${email}.",
|
||||
"delete": "Διαγραφή Φράσης",
|
||||
"deleteConfirm": "Η διαγραφή της φράσης δεν μπορεί να αναιρεθεί. Θέλετε να συνεχίσετε;",
|
||||
"locationLabel": "Προσθήκη Τοποθεσίας",
|
||||
"global": "Κοινές Φράσεις",
|
||||
"assistant": "Φράσεις Βοηθού"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Γρήγορη Πρόσβαση",
|
||||
"close": "Κλείσιμο",
|
||||
"select": "Επιλογή",
|
||||
"page": "Σελίδα",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"back": "Πίσω",
|
||||
"forward": "Μπρος",
|
||||
"multiple": "Πολλαπλή επιλογή"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Ρυθμίσεις Απορρήτου",
|
||||
"enable_privacy_mode": "Αποστολή ανώνυμων αναφορών σφαλμάτων και στατιστικών δεδομένων"
|
||||
},
|
||||
"assistant.icon.type": "Τύπος εικονιδίου μοντέλου",
|
||||
"assistant.icon.type.model": "Εικονίδιο μοντέλου",
|
||||
"assistant.icon.type.emoji": "Emoji",
|
||||
"assistant.icon.type.none": "Κανένα",
|
||||
"general.auto_check_update.title": "Αυτόματη ενημέρωση",
|
||||
"input.show_translate_confirm": "Εμφάνιση παραθύρου επιβεβαίωσης μετάφρασης",
|
||||
"messages.prompt": "Λήμμα προτροπής",
|
||||
"messages.input.enable_quick_triggers": "Ενεργοποίηση των '/' και '@' για γρήγορη πρόσβαση σε μενού",
|
||||
"messages.input.enable_delete_model": "Ενεργοποίηση διαγραφής μοντέλων/επισυναπτόμενων αρχείων με το πλήκτρο διαγραφής",
|
||||
"messages.math_engine.none": "Κανένα",
|
||||
"models.manage.add_listed": "Προσθήκη μοντέλων από τη λίστα",
|
||||
"models.manage.remove_listed": "Αφαίρεση μοντέλων από τη λίστα",
|
||||
"zoom.title": "Μεγέθυνση σελίδας"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": " οποιαδήποτε γλώσσα",
|
||||
@@ -1155,7 +1638,10 @@
|
||||
"scroll_sync.disable": "Απενεργοποίηση συγχρονισμού οριζόντιου μετακινήσεων",
|
||||
"scroll_sync.enable": "Ενεργοποίηση συγχρονισμού οριζόντιου μετακινήσεων",
|
||||
"title": "Μετάφραση",
|
||||
"tooltip.newline": "Αλλαγή γραμμής"
|
||||
"tooltip.newline": "Αλλαγή γραμμής",
|
||||
"menu": {
|
||||
"description": "Μεταφράστε το περιεχόμενο του τρέχοντος πεδίου εισαγωγής"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"quit": "Έξοδος",
|
||||
@@ -1169,4 +1655,4 @@
|
||||
"visualization": "προβολή"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,26 @@
|
||||
"tag.default": "Predeterminado",
|
||||
"tag.new": "Nuevo",
|
||||
"tag.system": "Sistema",
|
||||
"title": "Agente"
|
||||
"title": "Agente",
|
||||
"import": {
|
||||
"type": {
|
||||
"url": "URL",
|
||||
"file": "Archivo"
|
||||
},
|
||||
"error": {
|
||||
"url_required": "Por favor, introduzca la URL",
|
||||
"fetch_failed": "Error al obtener los datos de la URL",
|
||||
"invalid_format": "Formato de proxy no válido: faltan campos obligatorios"
|
||||
},
|
||||
"title": "Importar desde el exterior",
|
||||
"url_placeholder": "Ingrese la URL JSON",
|
||||
"select_file": "Seleccionar archivo",
|
||||
"button": "Importar",
|
||||
"file_filter": "Archivos JSON"
|
||||
},
|
||||
"export": {
|
||||
"agent": "Exportar Agente"
|
||||
}
|
||||
},
|
||||
"assistants": {
|
||||
"abbr": "Asistente",
|
||||
@@ -52,8 +71,31 @@
|
||||
"settings.reasoning_effort.low": "Corto",
|
||||
"settings.reasoning_effort.medium": "Medio",
|
||||
"settings.reasoning_effort.off": "Apagado",
|
||||
"settings.reasoning_effort.tip": "Solo compatible con modelos de razonamiento OpenAI o-series y Anthropic",
|
||||
"title": "Asistente"
|
||||
"title": "Asistente",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Frases comunes",
|
||||
"add": "Agregar frase",
|
||||
"edit": "Editar frase",
|
||||
"delete": "Eliminar frase",
|
||||
"deleteConfirm": "¿Está seguro de que desea eliminar esta frase?",
|
||||
"titleLabel": "Título",
|
||||
"titlePlaceholder": "Ingrese el título",
|
||||
"contentLabel": "Contenido",
|
||||
"contentPlaceholder": "Por favor, introduzca el contenido de la frase. Puede usar variables y luego presionar Tab para navegar rápidamente a las variables y modificarlas. Por ejemplo: \\nAyúdame a planificar una ruta desde ${from} hasta ${to}, y luego envíala a ${email}."
|
||||
},
|
||||
"settings.title": "Configuración del Asistente",
|
||||
"icon.type": "Ícono del Asistente",
|
||||
"settings.mcp": "Servidor MCP",
|
||||
"settings.mcp.enableFirst": "Habilite este servidor en la configuración de MCP primero",
|
||||
"settings.mcp.title": "Configuración MCP",
|
||||
"settings.mcp.noServersAvailable": "No hay servidores MCP disponibles. Agregue un servidor en la configuración",
|
||||
"settings.mcp.description": "Servidor MCP habilitado por defecto",
|
||||
"settings.knowledge_base.recognition.tip": "El agente utilizará la capacidad del modelo grande para el reconocimiento de intenciones y decidirá si necesita invocar la base de conocimientos para responder. Esta función dependerá de las capacidades del modelo",
|
||||
"settings.knowledge_base.recognition": "Invocar base de conocimientos",
|
||||
"settings.knowledge_base.recognition.off": "Búsqueda forzada",
|
||||
"settings.knowledge_base.recognition.on": "Reconocimiento de intención",
|
||||
"settings.reasoning_effort.default": "Por defecto",
|
||||
"settings.more": "Configuración del Asistente"
|
||||
},
|
||||
"auth": {
|
||||
"error": "Falló la obtención automática de la clave, por favor obténla manualmente",
|
||||
@@ -133,7 +175,8 @@
|
||||
"prev": "Mensaje anterior",
|
||||
"top": "Volver arriba",
|
||||
"bottom": "Volver abajo",
|
||||
"close": "Cerrar"
|
||||
"close": "Cerrar",
|
||||
"history": "Historial de chat"
|
||||
},
|
||||
"resend": "Reenviar",
|
||||
"save": "Guardar",
|
||||
@@ -176,7 +219,6 @@
|
||||
"topics.export.obsidian_created_placeholder": "Selecciona la fecha de creación",
|
||||
"topics.export.obsidian_export_failed": "Exportación fallida",
|
||||
"topics.export.obsidian_export_success": "Exportación exitosa",
|
||||
"topics.export.obsidian_not_configured": "Obsidian no configurado",
|
||||
"topics.export.obsidian_operate": "Modo de operación",
|
||||
"topics.export.obsidian_operate_append": "Agregar",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "Crear nuevo (si existe, sobrescribir)",
|
||||
@@ -203,7 +245,54 @@
|
||||
"topics.unpinned": "Quitar fijación",
|
||||
"translate": "Traducir",
|
||||
"input.generate_image": "Generar imagen",
|
||||
"input.generate_image_not_supported": "El modelo no soporta la generación de imágenes"
|
||||
"input.generate_image_not_supported": "El modelo no soporta la generación de imágenes",
|
||||
"history": {
|
||||
"assistant_node": "Asistente",
|
||||
"click_to_navigate": "Haga clic para ir al mensaje correspondiente",
|
||||
"coming_soon": "Próximamente: gráfico del flujo de chat",
|
||||
"no_messages": "No se encontraron mensajes",
|
||||
"start_conversation": "Inicie una conversación para ver el gráfico del flujo de chat",
|
||||
"title": "Historial de chat",
|
||||
"user_node": "Usuario",
|
||||
"view_full_content": "Ver contenido completo"
|
||||
},
|
||||
"input.translating": "Traduciendo...",
|
||||
"input.thinking": "Pensando",
|
||||
"input.thinking.mode.default": "Predeterminado",
|
||||
"input.thinking.mode.default.tip": "El modelo determinará automáticamente la cantidad de tokens a pensar",
|
||||
"input.thinking.mode.custom": "Personalizado",
|
||||
"input.thinking.mode.custom.tip": "Número máximo de tokens que puede procesar el modelo. Debe tenerse en cuenta el límite del contexto del modelo, de lo contrario se generará un error",
|
||||
"input.thinking.budget_exceeds_max": "El presupuesto de pensamiento excede el número máximo de tokens",
|
||||
"input.upload.upload_from_local": "Subir archivo local...",
|
||||
"input.web_search.builtin": "Integrada en el modelo",
|
||||
"input.web_search.builtin.enabled_content": "Usar la función de búsqueda web integrada en el modelo",
|
||||
"input.web_search.builtin.disabled_content": "La búsqueda web no es compatible con este modelo actualmente",
|
||||
"input.web_search.no_web_search": "Sin búsqueda web",
|
||||
"input.web_search.no_web_search.description": "No activar la función de búsqueda web",
|
||||
"settings.code_cacheable": "Almacenamiento en caché de bloques de código",
|
||||
"settings.code_cacheable.tip": "El almacenamiento en caché de bloques de código puede reducir el tiempo de representación de bloques largos, pero aumenta el uso de memoria",
|
||||
"settings.code_cache_max_size": "Límite de caché",
|
||||
"settings.code_cache_max_size.tip": "Límite de caracteres permitidos en caché (en miles), calculado según el código resaltado. La longitud del código resaltado suele ser mucho mayor que el texto plano.",
|
||||
"settings.code_cache_ttl": "Tiempo de vida de la caché",
|
||||
"settings.code_cache_ttl.tip": "Tiempo de expiración de la caché (en minutos)",
|
||||
"settings.code_cache_threshold": "Umbral de la caché",
|
||||
"settings.code_cache_threshold.tip": "Longitud mínima del código permitida para almacenarse en caché (en miles de caracteres), solo los bloques de código por encima de este umbral serán almacenados",
|
||||
"topics.export.md.reason": "Exportar como Markdown (incluye el razonamiento)",
|
||||
"topics.export.obsidian_vault": "Biblioteca",
|
||||
"topics.export.obsidian_vault_placeholder": "Seleccione el nombre de la biblioteca",
|
||||
"topics.export.obsidian_path": "Ruta",
|
||||
"topics.export.obsidian_path_placeholder": "Seleccione una ruta",
|
||||
"topics.export.obsidian_no_vaults": "No se encontró ninguna biblioteca de Obsidian",
|
||||
"topics.export.obsidian_loading": "Cargando...",
|
||||
"topics.export.obsidian_fetch_error": "Error al obtener las bibliotecas de Obsidian",
|
||||
"topics.export.obsidian_fetch_folders_error": "Error al obtener la estructura de carpetas",
|
||||
"topics.export.obsidian_no_vault_selected": "Por favor seleccione primero una biblioteca",
|
||||
"topics.export.obsidian_select_vault_first": "Por favor seleccione una biblioteca primero",
|
||||
"topics.export.obsidian_root_directory": "Directorio raíz",
|
||||
"topics.export.siyuan": "Exportar a SiYuan Notes",
|
||||
"topics.export.wait_for_title_naming": "Generando título...",
|
||||
"topics.export.title_naming_success": "Título generado exitosamente",
|
||||
"topics.export.title_naming_failed": "Fallo al generar el título, usando el título predeterminado"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Replegar",
|
||||
@@ -254,7 +343,16 @@
|
||||
"select": "Seleccionar",
|
||||
"topics": "Temas",
|
||||
"warning": "Advertencia",
|
||||
"you": "Usuario"
|
||||
"you": "Usuario",
|
||||
"sort": {
|
||||
"pinyin": "Ordenar por pinyin",
|
||||
"pinyin.asc": "Ordenar por pinyin ascendente",
|
||||
"pinyin.desc": "Ordenar por pinyin descendente"
|
||||
},
|
||||
"inspect": "Inspeccionar",
|
||||
"collapse": "Colapsar",
|
||||
"loading": "Cargando...",
|
||||
"reasoning_content": "Pensamiento profundo"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentación de Ayuda"
|
||||
@@ -280,7 +378,9 @@
|
||||
"title": "Error de renderizado",
|
||||
"description": "Error al renderizar la fórmula, por favor, compruebe si el formato de la fórmula es correcto"
|
||||
},
|
||||
"user_message_not_found": "No se pudo encontrar el mensaje original del usuario"
|
||||
"user_message_not_found": "No se pudo encontrar el mensaje original del usuario",
|
||||
"unknown": "Error desconocido",
|
||||
"pause_placeholder": "Interrumpido"
|
||||
},
|
||||
"export": {
|
||||
"assistant": "Asistente",
|
||||
@@ -393,7 +493,11 @@
|
||||
"topN_tooltip": "Número de resultados coincidentes devueltos, un valor más alto significa más resultados coincidentes, pero también consume más tokens",
|
||||
"url_added": "URL agregada",
|
||||
"url_placeholder": "Ingrese la URL, múltiples URLs separadas por enter",
|
||||
"urls": "URLs"
|
||||
"urls": "URLs",
|
||||
"dimensions": "Dimensión de incrustación",
|
||||
"dimensions_size_tooltip": "Tamaño de la dimensión de incrustación, cuanto mayor sea el valor, mayor será la dimensión de incrustación, pero también consumirá más Tokens",
|
||||
"dimensions_size_placeholder": "Valor predeterminado (no recomendado modificar)",
|
||||
"dimensions_size_too_large": "La dimensión de incrustación no puede exceder el límite del contexto del modelo ({{max_context}})"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Árabe",
|
||||
@@ -505,20 +609,63 @@
|
||||
"completed": "Completado",
|
||||
"invoking": "En llamada",
|
||||
"raw": "Crudo",
|
||||
"preview": "Vista previa"
|
||||
"preview": "Vista previa",
|
||||
"error": "Se ha producido un error"
|
||||
},
|
||||
"topic.added": "Tema agregado con éxito",
|
||||
"upgrade.success.button": "Reiniciar",
|
||||
"upgrade.success.content": "Reinicie para completar la actualización",
|
||||
"upgrade.success.title": "Actualización exitosa",
|
||||
"warn.notion.exporting": "Se está exportando a Notion, ¡no solicite nuevamente la exportación!",
|
||||
"warning.rate.limit": "Envío demasiado frecuente, espere {{seconds}} segundos antes de intentarlo de nuevo"
|
||||
"warning.rate.limit": "Envío demasiado frecuente, espere {{seconds}} segundos antes de intentarlo de nuevo",
|
||||
"agents": {
|
||||
"imported": "Importado con éxito",
|
||||
"import.error": "Error al importar"
|
||||
},
|
||||
"citation": "{{count}} contenido citado",
|
||||
"error.invalid.nutstore": "Configuración de Nutstore no válida",
|
||||
"error.invalid.nutstore_token": "Token de Nutstore no válido",
|
||||
"processing": "Procesando...",
|
||||
"error.siyuan.export": "Error al exportar la nota de Siyuan, verifique el estado de la conexión y revise la configuración según la documentación",
|
||||
"error.siyuan.no_config": "No se ha configurado la dirección API o el token de Siyuan",
|
||||
"success.siyuan.export": "Exportado a Siyuan exitosamente",
|
||||
"warn.yuque.exporting": "Exportando Yuque, ¡no solicite la exportación nuevamente!",
|
||||
"warn.siyuan.exporting": "Exportando a Siyuan, ¡no solicite la exportación nuevamente!",
|
||||
"download.success": "Descarga exitosa",
|
||||
"download.failed": "Descarga fallida"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Agregar al panel lateral",
|
||||
"sidebar.remove.title": "Quitar del panel lateral",
|
||||
"sidebar.hide.title": "Ocultar mini programa",
|
||||
"title": "Mini programa"
|
||||
"title": "Mini programa",
|
||||
"popup": {
|
||||
"refresh": "Actualizar",
|
||||
"close": "Cerrar la aplicación",
|
||||
"minimize": "Minimizar la aplicación",
|
||||
"devtools": "Herramientas de desarrollo",
|
||||
"openExternal": "Abrir en el navegador",
|
||||
"rightclick_copyurl": "Copiar URL con clic derecho",
|
||||
"open_link_external_on": "Actual: Abrir enlaces en el navegador",
|
||||
"open_link_external_off": "Actual: Abrir enlaces en ventana predeterminada"
|
||||
},
|
||||
"sidebar": {
|
||||
"add": {
|
||||
"title": "Agregar a la barra lateral"
|
||||
},
|
||||
"remove": {
|
||||
"title": "Eliminar de la barra lateral"
|
||||
},
|
||||
"remove_custom": {
|
||||
"title": "Eliminar aplicación personalizada"
|
||||
},
|
||||
"hide": {
|
||||
"title": "Ocultar"
|
||||
},
|
||||
"close": {
|
||||
"title": "Cerrar"
|
||||
},
|
||||
"closeall": {
|
||||
"title": "Cerrar todo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"miniwindow": {
|
||||
"clipboard": {
|
||||
@@ -534,13 +681,17 @@
|
||||
"copy_last_message": "Presione C para copiar",
|
||||
"esc": "Presione ESC {{action}}",
|
||||
"esc_back": "Volver",
|
||||
"esc_close": "Cerrar ventana"
|
||||
"esc_close": "Cerrar ventana",
|
||||
"backspace_clear": "Presione Retroceso para borrar"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": {
|
||||
"empty": "Pregunta a {{model}} para obtener ayuda...",
|
||||
"title": "¿Qué deseas hacer con el texto de abajo?"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"pin": "Fijar en la parte superior"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
@@ -577,7 +728,9 @@
|
||||
"free": "Gratis",
|
||||
"rerank": "Reclasificar",
|
||||
"websearch": "Búsqueda en línea"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "Actualmente, el modelo de reordenamiento no admite este proveedor ({{provider}})",
|
||||
"enable_tool_use": "Habilitar uso de herramientas"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Expandir cuadro de diálogo",
|
||||
@@ -609,7 +762,59 @@
|
||||
"regenerate.confirm": "Esto sobrescribirá las imágenes generadas, ¿desea continuar?",
|
||||
"seed": "Semilla aleatoria",
|
||||
"seed_tip": "La misma semilla y la misma sugerencia generarán imágenes similares",
|
||||
"title": "Imagen"
|
||||
"title": "Imagen",
|
||||
"mode": {
|
||||
"generate": "Generar imagen",
|
||||
"edit": "Editar",
|
||||
"remix": "Mezclar",
|
||||
"upscale": "Ampliar"
|
||||
},
|
||||
"generate": {
|
||||
"model_tip": "Versión del modelo: V2 es el modelo más reciente de la interfaz, V2A es un modelo rápido, V_1 es el modelo inicial y _TURBO es la versión acelerada",
|
||||
"number_images_tip": "Número de imágenes generadas a la vez",
|
||||
"seed_tip": "Controla la aleatoriedad en la generación de imágenes, útil para reproducir resultados idénticos",
|
||||
"negative_prompt_tip": "Describe elementos que no deseas en la imagen. Solo compatible con las versiones V_1, V_1_TURBO, V_2 y V_2_TURBO",
|
||||
"magic_prompt_option_tip": "Optimización inteligente de indicaciones para mejorar los resultados de generación",
|
||||
"style_type_tip": "Estilo de generación de imágenes, solo aplicable para la versión V_2 y posteriores"
|
||||
},
|
||||
"edit": {
|
||||
"image_file": "Imagen editada",
|
||||
"model_tip": "La edición local solo es compatible con las versiones V_2 y V_2_TURBO",
|
||||
"number_images_tip": "Número de resultados de edición generados",
|
||||
"style_type_tip": "Estilo de la imagen editada, solo aplicable para la versión V_2 y posteriores",
|
||||
"seed_tip": "Controla la aleatoriedad de los resultados de edición",
|
||||
"magic_prompt_option_tip": "Optimización inteligente de las palabras clave de edición"
|
||||
},
|
||||
"remix": {
|
||||
"model_tip": "Seleccione la versión del modelo de inteligencia artificial para usar en el remix",
|
||||
"image_file": "Imagen de referencia",
|
||||
"image_weight": "Peso de la imagen de referencia",
|
||||
"image_weight_tip": "Ajuste el grado de influencia de la imagen de referencia",
|
||||
"number_images_tip": "Número de resultados de remix generados",
|
||||
"seed_tip": "Controla la aleatoriedad de los resultados del remix",
|
||||
"style_type_tip": "Estilo de la imagen tras el remix, solo aplicable a partir de la versión V_2",
|
||||
"negative_prompt_tip": "Describa los elementos que no desea ver en los resultados del remix",
|
||||
"magic_prompt_option_tip": "Optimización inteligente de las palabras clave para el remix"
|
||||
},
|
||||
"upscale": {
|
||||
"image_file": "Imagen que se desea ampliar",
|
||||
"resemblance": "Similitud",
|
||||
"resemblance_tip": "Controla el nivel de similitud entre el resultado ampliado y la imagen original",
|
||||
"detail": "Detalle",
|
||||
"detail_tip": "Controla el grado de realce de los detalles en la imagen ampliada",
|
||||
"number_images_tip": "Número de resultados de ampliación generados",
|
||||
"seed_tip": "Controla la aleatoriedad del resultado de la ampliación",
|
||||
"magic_prompt_option_tip": "Optimización inteligente de las palabras clave para la ampliación"
|
||||
},
|
||||
"magic_prompt_option": "Mejora de indicación",
|
||||
"model": "Versión",
|
||||
"aspect_ratio": "Relación de aspecto",
|
||||
"style_type": "Estilo",
|
||||
"learn_more": "Más información",
|
||||
"prompt_placeholder_edit": "Introduce la descripción de tu imagen, utiliza comillas dobles \" \" para texto a dibujar",
|
||||
"proxy_required": "Actualmente es necesario tener un proxy activo para ver las imágenes generadas, en el futuro se soportará conexión directa desde China",
|
||||
"image_file_required": "Por favor, carga una imagen primero",
|
||||
"image_file_retry": "Vuelve a cargar la imagen"
|
||||
},
|
||||
"plantuml": {
|
||||
"download": {
|
||||
@@ -672,7 +877,9 @@
|
||||
"xirang": "Telecom Nube XiRang",
|
||||
"yi": "Cero Uno Todo",
|
||||
"zhinao": "360 Inteligente",
|
||||
"zhipu": "ZhiPu IA"
|
||||
"zhipu": "ZhiPu IA",
|
||||
"voyageai": "Voyage AI",
|
||||
"qiniu": "Qiniu Yun"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "¿Está seguro de que desea restaurar los datos?",
|
||||
@@ -714,7 +921,6 @@
|
||||
"advanced.title": "Configuración avanzada",
|
||||
"assistant": "Asistente predeterminado",
|
||||
"assistant.model_params": "Parámetros del modelo",
|
||||
"assistant.show.icon": "Mostrar icono del modelo",
|
||||
"assistant.title": "Asistente predeterminado",
|
||||
"data": {
|
||||
"app_data": "Datos de la aplicación",
|
||||
@@ -780,13 +986,13 @@
|
||||
"notion.split_size_placeholder": "Introduzca el límite de bloques por página (predeterminado 90)",
|
||||
"notion.title": "Configuración de Notion",
|
||||
"obsidian": {
|
||||
"folder": "Carpeta",
|
||||
"folder_placeholder": "Introduce el nombre de la carpeta",
|
||||
"tags": "Etiquetas globales",
|
||||
"tags_placeholder": "Introduce el nombre de la etiqueta, múltiples etiquetas separadas por comas, Obsidian no admite números puros",
|
||||
"title": "Configuración de Obsidian",
|
||||
"vault": "Custodia",
|
||||
"vault_placeholder": "Introduce el nombre de la custodia"
|
||||
"default_vault": "Repositorio Obsidian predeterminado",
|
||||
"default_vault_placeholder": "Seleccione un repositorio Obsidian predeterminado",
|
||||
"default_vault_loading": "Obteniendo repositorios Obsidian...",
|
||||
"default_vault_no_vaults": "No se encontraron repositorios Obsidian",
|
||||
"default_vault_fetch_error": "Error al obtener los repositorios Obsidian",
|
||||
"default_vault_export_failed": "Exportación fallida"
|
||||
},
|
||||
"title": "Configuración de datos",
|
||||
"webdav": {
|
||||
@@ -818,7 +1024,26 @@
|
||||
"title": "WebDAV",
|
||||
"user": "Nombre de usuario WebDAV",
|
||||
"maxBackups": "Número máximo de copias de seguridad",
|
||||
"maxBackups.unlimited": "Sin límite"
|
||||
"maxBackups.unlimited": "Sin límite",
|
||||
"backup.manager.title": "Gestión de copias de seguridad",
|
||||
"backup.manager.refresh": "Actualizar",
|
||||
"backup.manager.delete.selected": "Eliminar seleccionados",
|
||||
"backup.manager.delete.text": "Eliminar",
|
||||
"backup.manager.restore.text": "Restaurar",
|
||||
"backup.manager.restore.success": "Restauración exitosa, la aplicación se actualizará en unos segundos",
|
||||
"backup.manager.restore.error": "Fallo en la restauración",
|
||||
"backup.manager.delete.confirm.title": "Confirmar eliminación",
|
||||
"backup.manager.delete.confirm.single": "¿Está seguro de que desea eliminar el archivo de copia de seguridad \"{{fileName}}\"? Esta acción no se puede deshacer.",
|
||||
"backup.manager.delete.confirm.multiple": "¿Está seguro de que desea eliminar los {{count}} archivos de copia de seguridad seleccionados? Esta acción no se puede deshacer.",
|
||||
"backup.manager.delete.success.single": "Eliminación exitosa",
|
||||
"backup.manager.delete.success.multiple": "Se eliminaron exitosamente {{count}} archivos de copia de seguridad",
|
||||
"backup.manager.delete.error": "Fallo al eliminar",
|
||||
"backup.manager.fetch.error": "No se pudo obtener el archivo de copia de seguridad",
|
||||
"backup.manager.select.files.delete": "Seleccione los archivos de copia de seguridad a eliminar",
|
||||
"backup.manager.columns.fileName": "Nombre del archivo",
|
||||
"backup.manager.columns.modifiedTime": "Fecha de modificación",
|
||||
"backup.manager.columns.size": "Tamaño",
|
||||
"backup.manager.columns.actions": "Acciones"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -834,16 +1059,73 @@
|
||||
"title": "Configuración de Yuque",
|
||||
"token": "Token de Yuque",
|
||||
"token_placeholder": "Ingrese el Token de Yuque"
|
||||
}
|
||||
},
|
||||
"export_menu": {
|
||||
"title": "Exportar configuración del menú",
|
||||
"image": "Exportar como imagen",
|
||||
"markdown": "Exportar a Markdown",
|
||||
"markdown_reason": "Exportar a Markdown (con pensamiento incluido)",
|
||||
"notion": "Exportar a Notion",
|
||||
"yuque": "Exportar a Yuque",
|
||||
"obsidian": "Exportar a Obsidian",
|
||||
"siyuan": "Exportar a Siyuan Notes",
|
||||
"joplin": "Exportar a Joplin",
|
||||
"docx": "Exportar a Word"
|
||||
},
|
||||
"siyuan": {
|
||||
"check": {
|
||||
"title": "Prueba de conexión",
|
||||
"button": "Probar",
|
||||
"empty_config": "Por favor, complete la dirección API y el token",
|
||||
"success": "Conexión exitosa",
|
||||
"fail": "Fallo en la conexión, verifique la dirección API y el token",
|
||||
"error": "Error inesperado, verifique la conexión de red"
|
||||
},
|
||||
"title": "Configuración de Siyuan Notas",
|
||||
"api_url": "Dirección API",
|
||||
"api_url_placeholder": "Ejemplo: http://127.0.0.1:6806",
|
||||
"token": "Token API",
|
||||
"token.help": "Obtener en Siyuan Notas -> Configuración -> Acerca de",
|
||||
"token_placeholder": "Por favor ingrese el token de Siyuan Notas",
|
||||
"box_id": "ID del Cuaderno",
|
||||
"box_id_placeholder": "Por favor ingrese el ID del cuaderno",
|
||||
"root_path": "Ruta raíz del documento",
|
||||
"root_path_placeholder": "Ejemplo: /CherryStudio"
|
||||
},
|
||||
"nutstore": {
|
||||
"title": "Configuración de Nutstore",
|
||||
"isLogin": "Iniciado sesión",
|
||||
"notLogin": "No iniciado sesión",
|
||||
"login.button": "Iniciar Sesión",
|
||||
"logout.button": "Cerrar Sesión",
|
||||
"logout.title": "¿Seguro que quieres cerrar la sesión de Nutstore?",
|
||||
"logout.content": "Después de cerrar sesión no podrás hacer copias de seguridad ni restaurar desde Nutstore",
|
||||
"checkConnection.name": "Verificar conexión",
|
||||
"checkConnection.success": "Conexión con Nutstore establecida",
|
||||
"checkConnection.fail": "Fallo en la conexión con Nutstore",
|
||||
"username": "Nombre de usuario de Nutstore",
|
||||
"path": "Ruta de almacenamiento de Nutstore",
|
||||
"path.placeholder": "Por favor ingrese la ruta de almacenamiento de Nutstore",
|
||||
"backup.button": "Hacer copia de seguridad en Nutstore",
|
||||
"restore.button": "Restaurar desde Nutstore",
|
||||
"pathSelector.title": "Ruta de almacenamiento de Nutstore",
|
||||
"pathSelector.return": "Volver",
|
||||
"pathSelector.currentPath": "Ruta actual",
|
||||
"new_folder.button.confirm": "Aceptar",
|
||||
"new_folder.button.cancel": "Cancelar",
|
||||
"new_folder.button": "Crear carpeta"
|
||||
},
|
||||
"divider.basic": "Configuración básica",
|
||||
"divider.cloud_storage": "Configuración de almacenamiento en la nube",
|
||||
"divider.export_settings": "Configuración de exportación",
|
||||
"divider.third_party": "Conexiones de terceros",
|
||||
"message_title.use_topic_naming.title": "Usar el modelo de nombramiento temático para crear títulos de mensajes exportados",
|
||||
"message_title.use_topic_naming.help": "Al activarlo, se utilizará el modelo de nombramiento temático para generar títulos de mensajes exportados. Esta opción también afectará a todos los métodos de exportación mediante Markdown."
|
||||
},
|
||||
"display.assistant.title": "Configuración del asistente",
|
||||
"display.custom.css": "CSS personalizado",
|
||||
"display.custom.css.cherrycss": "Obtener desde cherrycss.com",
|
||||
"display.custom.css.placeholder": "/* Escribe tu CSS personalizado aquí */",
|
||||
"display.minApp.disabled": "Miniprogramas ocultos",
|
||||
"display.minApp.empty": "Arrastra los miniprogramas que quieres ocultar desde la izquierda aquí",
|
||||
"display.minApp.title": "Configuración de visualización de miniprogramas",
|
||||
"display.minApp.visible": "Miniprogramas visibles",
|
||||
"display.sidebar.chat.hiddenMessage": "El asistente es una función básica y no se puede ocultar",
|
||||
"display.sidebar.disabled": "Iconos ocultos",
|
||||
"display.sidebar.empty": "Arrastra las funciones que deseas ocultar desde la izquierda aquí",
|
||||
@@ -864,7 +1146,6 @@
|
||||
"general.display.title": "Configuración de visualización",
|
||||
"general.emoji_picker": "Selector de emojis",
|
||||
"general.image_upload": "Carga de imágenes",
|
||||
"general.manually_check_update.title": "Desactivar verificación de actualizaciones",
|
||||
"general.reset.button": "Restablecer",
|
||||
"general.reset.title": "Restablecer datos",
|
||||
"general.restore.button": "Restaurar",
|
||||
@@ -930,11 +1211,94 @@
|
||||
"serverPlural": "Servidores",
|
||||
"serverSingular": "Servidor",
|
||||
"title": "Servidores MCP",
|
||||
"toggleError": "Fallo al cambiar",
|
||||
"type": "Tipo",
|
||||
"updateError": "Fallo al actualizar servidor",
|
||||
"updateSuccess": "Servidor actualizado exitosamente",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"errors": {
|
||||
"32000": "El servidor MCP no se pudo iniciar, verifique si los parámetros están completos según la guía"
|
||||
},
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"description": "Descripción",
|
||||
"tools": "Herramientas",
|
||||
"prompts": "Indicaciones",
|
||||
"resources": "Recursos"
|
||||
},
|
||||
"tools": {
|
||||
"inputSchema": "Esquema de entrada",
|
||||
"availableTools": "Herramientas disponibles",
|
||||
"noToolsAvailable": "No hay herramientas disponibles",
|
||||
"loadError": "Error al cargar las herramientas"
|
||||
},
|
||||
"prompts": {
|
||||
"availablePrompts": "Indicaciones disponibles",
|
||||
"noPromptsAvailable": "No hay indicaciones disponibles",
|
||||
"arguments": "Argumentos",
|
||||
"requiredField": "Campo obligatorio",
|
||||
"genericError": "Error al obtener la indicación",
|
||||
"loadError": "Fallo al cargar la indicación"
|
||||
},
|
||||
"resources": {
|
||||
"noResourcesAvailable": "No hay recursos disponibles",
|
||||
"availableResources": "Recursos disponibles",
|
||||
"uri": "URI",
|
||||
"mimeType": "Tipo MIME",
|
||||
"size": "Tamaño",
|
||||
"blob": "Datos binarios",
|
||||
"blobInvisible": "Datos binarios ocultos",
|
||||
"text": "Texto"
|
||||
},
|
||||
"types": {
|
||||
"inMemory": "Integrado",
|
||||
"sse": "SSE",
|
||||
"streamableHttp": "En secuencia",
|
||||
"stdio": "STDIO"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizar Servidor",
|
||||
"selectProvider": "Seleccionar proveedor:",
|
||||
"discoverMcpServers": "Detectar servidores MCP",
|
||||
"discoverMcpServersDescription": "Acceder a la plataforma para detectar servidores MCP disponibles",
|
||||
"getToken": "Obtener token de API",
|
||||
"getTokenDescription": "Obtener un token de API personal desde su cuenta",
|
||||
"setToken": "Ingrese su token",
|
||||
"tokenRequired": "Se requiere token de API",
|
||||
"tokenPlaceholder": "Introduzca el token de API aquí",
|
||||
"button": "Sincronizar",
|
||||
"error": "Error al sincronizar el servidor MCP",
|
||||
"success": "Servidor MCP sincronizado correctamente",
|
||||
"unauthorized": "Sincronización no autorizada",
|
||||
"noServersAvailable": "No hay servidores MCP disponibles"
|
||||
},
|
||||
"sse": "Eventos enviados por el servidor (sse)",
|
||||
"streamableHttp": "HTTP transmisible (streamableHttp)",
|
||||
"stdio": "Entrada/Salida estándar (stdio)",
|
||||
"inMemory": "En memoria",
|
||||
"headers": "Encabezados",
|
||||
"headersTooltip": "Encabezados personalizados para solicitudes HTTP",
|
||||
"searchNpx": "Buscar MCP",
|
||||
"newServer": "Servidor MCP",
|
||||
"startError": "Inicio fallido",
|
||||
"editMcpJson": "Editar configuración MCP",
|
||||
"installHelp": "Obtener ayuda de instalación",
|
||||
"deleteServer": "Eliminar servidor",
|
||||
"deleteServerConfirm": "¿Está seguro de que desea eliminar este servidor?",
|
||||
"registry": "Repositorio de paquetes",
|
||||
"registryTooltip": "Seleccione un repositorio para instalar paquetes, útil para resolver problemas de red con el repositorio predeterminado.",
|
||||
"registryDefault": "Predeterminado",
|
||||
"not_support": "El modelo no es compatible",
|
||||
"user": "Usuario",
|
||||
"system": "Sistema",
|
||||
"timeout": "Tiempo de espera",
|
||||
"timeoutTooltip": "Tiempo de espera (en segundos) para las solicitudes a este servidor; el valor predeterminado es 60 segundos",
|
||||
"provider": "Proveedor",
|
||||
"providerUrl": "URL del proveedor",
|
||||
"logoUrl": "URL del logotipo",
|
||||
"tags": "Etiquetas",
|
||||
"tagsPlaceholder": "Ingrese etiquetas",
|
||||
"providerPlaceholder": "Nombre del proveedor",
|
||||
"advancedSettings": "Configuración avanzada"
|
||||
},
|
||||
"messages.divider": "Separador de mensajes",
|
||||
"messages.grid_columns": "Número de columnas en la cuadrícula de mensajes",
|
||||
@@ -1046,13 +1410,29 @@
|
||||
"docs_more_details": "Obtener más detalles",
|
||||
"get_api_key": "Haga clic aquí para obtener la clave",
|
||||
"is_not_support_array_content": "Activar modo compatible",
|
||||
"no_models": "Agregue un modelo primero antes de verificar la conexión API",
|
||||
"not_checked": "No verificado",
|
||||
"remove_duplicate_keys": "Eliminar claves duplicadas",
|
||||
"remove_invalid_keys": "Eliminar claves inválidas",
|
||||
"search": "Buscar plataforma de modelos...",
|
||||
"search_placeholder": "Buscar ID o nombre del modelo",
|
||||
"title": "Servicio de modelos"
|
||||
"title": "Servicio de modelos",
|
||||
"oauth": {
|
||||
"button": "Iniciar sesión con la cuenta de {{provider}}",
|
||||
"description": "Este servicio es proporcionado por <website>{{provider}}</website>",
|
||||
"official_website": "Sitio web oficial"
|
||||
},
|
||||
"notes": {
|
||||
"title": "Nota del modelo",
|
||||
"placeholder": "Por favor, introduzca el contenido en formato Markdown...",
|
||||
"markdown_editor_default_value": "Área de vista previa"
|
||||
},
|
||||
"basic_auth": "Autenticación HTTP",
|
||||
"basic_auth.tip": "Aplicable para instancias desplegadas a través del servidor (ver documento). Actualmente solo se admite el esquema Basic (RFC7617).",
|
||||
"basic_auth.user_name": "Nombre de usuario",
|
||||
"basic_auth.user_name.tip": "Déjelo vacío para desactivar",
|
||||
"basic_auth.password": "Contraseña",
|
||||
"bills": "Facturas",
|
||||
"no_models_for_check": "No hay modelos disponibles para revisar (por ejemplo, modelos de conversación)"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1129,8 +1509,111 @@
|
||||
"description": "Tavily es un motor de búsqueda diseñado específicamente para agentes de IA, proporcionando resultados en tiempo real, precisos, sugerencias de consulta inteligentes y capacidades de investigación profundas",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Búsqueda en la web"
|
||||
}
|
||||
"title": "Búsqueda en la web",
|
||||
"overwrite": "Sobrescribir proveedor de búsqueda",
|
||||
"overwrite_tooltip": "Forzar el uso del proveedor de búsqueda en lugar del modelo de lenguaje grande para realizar búsquedas",
|
||||
"subscribe": "Suscripción a lista negra",
|
||||
"subscribe_update": "Actualizar ahora",
|
||||
"subscribe_add": "Agregar suscripción",
|
||||
"subscribe_url": "Dirección del origen de la suscripción",
|
||||
"subscribe_name": "Nombre alternativo",
|
||||
"subscribe_name.placeholder": "Nombre alternativo que se usará cuando el origen de la suscripción descargado no tenga un nombre",
|
||||
"subscribe_add_success": "¡Origen de la suscripción agregado correctamente!",
|
||||
"subscribe_delete": "Eliminar origen de la suscripción",
|
||||
"apikey": "Clave API",
|
||||
"free": "Gratis",
|
||||
"content_limit": "Límite de longitud del contenido",
|
||||
"content_limit_tooltip": "Limita la longitud del contenido de los resultados de búsqueda; el contenido excedente será truncado"
|
||||
},
|
||||
"miniapps": {
|
||||
"open_link_external": {
|
||||
"title": "Abrir enlace en nueva ventana del navegador"
|
||||
},
|
||||
"custom": {
|
||||
"title": "Aplicación Pequeña Personalizada",
|
||||
"edit_title": "Editar Aplicación Pequeña Personalizada",
|
||||
"save_success": "La aplicación pequeña personalizada se ha guardado correctamente.",
|
||||
"save_error": "No se pudo guardar la aplicación pequeña personalizada.",
|
||||
"remove_success": "La aplicación pequeña personalizada se eliminó correctamente.",
|
||||
"remove_error": "No se pudo eliminar la aplicación pequeña personalizada.",
|
||||
"logo_upload_success": "El logo se cargó correctamente.",
|
||||
"logo_upload_error": "No se pudo cargar el logo.",
|
||||
"id": "ID",
|
||||
"id_error": "El campo ID es obligatorio.",
|
||||
"id_placeholder": "Por favor, introduzca el ID",
|
||||
"name": "Nombre",
|
||||
"name_error": "El campo Nombre es obligatorio.",
|
||||
"name_placeholder": "Por favor, introduzca el nombre",
|
||||
"url": "URL",
|
||||
"url_error": "El campo URL es obligatorio.",
|
||||
"url_placeholder": "Por favor, introduzca la URL",
|
||||
"logo": "Logo",
|
||||
"logo_url": "URL del Logo",
|
||||
"logo_file": "Cargar Archivo del Logo",
|
||||
"logo_url_label": "URL del Logo",
|
||||
"logo_url_placeholder": "Por favor, introduzca la URL del logo",
|
||||
"logo_upload_label": "Cargar Logo",
|
||||
"logo_upload_button": "Cargar",
|
||||
"save": "Guardar",
|
||||
"edit_description": "Edite aquí la configuración de su aplicación pequeña personalizada. Cada aplicación debe incluir los campos id, name, url y logo.",
|
||||
"placeholder": "Introduzca la configuración de la aplicación pequeña personalizada (en formato JSON)",
|
||||
"duplicate_ids": "Se encontraron IDs duplicados: {{ids}}",
|
||||
"conflicting_ids": "Conflictos con IDs de aplicaciones predeterminadas: {{ids}}"
|
||||
},
|
||||
"title": "Configuración de miniaplicaciones",
|
||||
"disabled": "Miniaplicaciones ocultas",
|
||||
"empty": "Arrastra aquí las miniaplicaciones que deseas ocultar desde la izquierda",
|
||||
"visible": "Miniaplicaciones visibles",
|
||||
"cache_settings": "Configuración de caché",
|
||||
"cache_title": "Cantidad de miniaplicaciones en caché",
|
||||
"cache_description": "Establece el número máximo de miniaplicaciones que pueden permanecer activas simultáneamente",
|
||||
"reset_tooltip": "Restablecer a los valores predeterminados",
|
||||
"display_title": "Configuración de visualización de miniaplicaciones",
|
||||
"sidebar_title": "Visualización de miniaplicaciones activas en la barra lateral",
|
||||
"sidebar_description": "Configura si se muestra o no en la barra lateral la miniaplicación activa",
|
||||
"cache_change_notice": "Los cambios surtirán efecto cuando el número de miniaplicaciones abiertas aumente o disminuya hasta alcanzar el valor configurado"
|
||||
},
|
||||
"quickPhrase": {
|
||||
"title": "Frases rápidas",
|
||||
"add": "Agregar frase",
|
||||
"edit": "Editar frase",
|
||||
"titleLabel": "Título",
|
||||
"contentLabel": "Contenido",
|
||||
"titlePlaceholder": "Ingrese el título de la frase",
|
||||
"contentPlaceholder": "Ingrese el contenido de la frase. Se admite el uso de variables, y luego puede presionar Tab para ubicar rápidamente la variable y modificarla. Por ejemplo: \\nAyúdame a planificar la ruta desde ${desde} hasta ${hasta}, y luego envíala a ${correo}.",
|
||||
"delete": "Eliminar frase",
|
||||
"deleteConfirm": "Una vez eliminada, la frase no podrá recuperarse. ¿Desea continuar?",
|
||||
"locationLabel": "Agregar ubicación",
|
||||
"global": "Frase global",
|
||||
"assistant": "Frase de asistente"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Menú de acceso rápido",
|
||||
"close": "Cerrar",
|
||||
"select": "Seleccionar",
|
||||
"page": "Página",
|
||||
"confirm": "Confirmar",
|
||||
"back": "Atrás",
|
||||
"forward": "Adelante",
|
||||
"multiple": "Selección múltiple"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Configuración de privacidad",
|
||||
"enable_privacy_mode": "Enviar informes de errores y estadísticas de forma anónima"
|
||||
},
|
||||
"assistant.icon.type": "Tipo de ícono del modelo",
|
||||
"assistant.icon.type.model": "Ícono del modelo",
|
||||
"assistant.icon.type.emoji": "Emoji",
|
||||
"assistant.icon.type.none": "No mostrar",
|
||||
"general.auto_check_update.title": "Actualización automática",
|
||||
"input.show_translate_confirm": "Mostrar diálogo de confirmación de traducción",
|
||||
"messages.prompt": "Palabra de indicación",
|
||||
"messages.input.enable_quick_triggers": "Habilitar menú rápido con '/' y '@'",
|
||||
"messages.input.enable_delete_model": "Habilitar la eliminación con la tecla de borrado para modelos/archivos adjuntos introducidos",
|
||||
"messages.math_engine.none": "Ninguno",
|
||||
"models.manage.add_listed": "Agregar modelo de la lista",
|
||||
"models.manage.remove_listed": "Eliminar modelo de la lista",
|
||||
"zoom.title": "Zoom de página"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "cualquier idioma",
|
||||
@@ -1155,7 +1638,10 @@
|
||||
"scroll_sync.disable": "Deshabilitar sincronización de desplazamiento",
|
||||
"scroll_sync.enable": "Habilitar sincronización de desplazamiento",
|
||||
"title": "Traducción",
|
||||
"tooltip.newline": "Salto de línea"
|
||||
"tooltip.newline": "Salto de línea",
|
||||
"menu": {
|
||||
"description": "Traducir el contenido del campo de entrada actual"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"quit": "Salir",
|
||||
@@ -1169,4 +1655,4 @@
|
||||
"visualization": "Visualización"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,26 @@
|
||||
"tag.default": "Par défaut",
|
||||
"tag.new": "Nouveau",
|
||||
"tag.system": "Système",
|
||||
"title": "Agent intelligent"
|
||||
"title": "Agent intelligent",
|
||||
"import": {
|
||||
"type": {
|
||||
"url": "URL",
|
||||
"file": "Fichier"
|
||||
},
|
||||
"error": {
|
||||
"url_required": "Veuillez entrer l'URL",
|
||||
"fetch_failed": "Échec de la récupération des données depuis l'URL",
|
||||
"invalid_format": "Format de proxy invalide : champs obligatoires manquants"
|
||||
},
|
||||
"title": "Импорт из внешнего источника",
|
||||
"url_placeholder": "Введите URL JSON",
|
||||
"select_file": "Выбрать файл",
|
||||
"button": "Импортировать",
|
||||
"file_filter": "Файлы JSON"
|
||||
},
|
||||
"export": {
|
||||
"agent": "Экспортировать агента"
|
||||
}
|
||||
},
|
||||
"assistants": {
|
||||
"abbr": "Aide",
|
||||
@@ -52,8 +71,31 @@
|
||||
"settings.reasoning_effort.low": "Court",
|
||||
"settings.reasoning_effort.medium": "Moyen",
|
||||
"settings.reasoning_effort.off": "Off",
|
||||
"settings.reasoning_effort.tip": "Prise en charge uniquement des modèles de raisonnement OpenAI o-series et Anthropic",
|
||||
"title": "Agent"
|
||||
"title": "Agent",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Популярные фразы",
|
||||
"add": "Добавить фразу",
|
||||
"edit": "Редактировать фразу",
|
||||
"delete": "Удалить фразу",
|
||||
"deleteConfirm": "Вы уверены, что хотите удалить эту фразу?",
|
||||
"titleLabel": "Заголовок",
|
||||
"titlePlaceholder": "Введите заголовок",
|
||||
"contentLabel": "Содержание",
|
||||
"contentPlaceholder": "Введите содержание фразы. Поддерживаются переменные, после этого нажмите Tab для быстрого перехода к переменной и изменения её значения. Например:\\n Планируй маршрут из ${from} в ${to}, а затем отправь его на ${email}."
|
||||
},
|
||||
"settings.title": "Paramètres de l'assistant",
|
||||
"icon.type": "Icône de l'assistant",
|
||||
"settings.mcp": "Serveur MCP",
|
||||
"settings.mcp.enableFirst": "Veuillez d'abord activer ce serveur dans les paramètres MCP",
|
||||
"settings.mcp.title": "Paramètres MCP",
|
||||
"settings.mcp.noServersAvailable": "Aucun serveur MCP disponible. Veuillez ajouter un serveur dans les paramètres",
|
||||
"settings.mcp.description": "Serveur MCP activé par défaut",
|
||||
"settings.knowledge_base.recognition.tip": "L'agent utilisera la capacité du grand modèle à reconnaître les intentions afin de déterminer si la base de connaissances doit être utilisée pour répondre. Cette fonctionnalité dépend des capacités du modèle",
|
||||
"settings.knowledge_base.recognition": "Utiliser la base de connaissances",
|
||||
"settings.knowledge_base.recognition.off": "Recherche forcée",
|
||||
"settings.knowledge_base.recognition.on": "Reconnaissance des intentions",
|
||||
"settings.reasoning_effort.default": "Par défaut",
|
||||
"settings.more": "Paramètres de l'assistant"
|
||||
},
|
||||
"auth": {
|
||||
"error": "Échec de l'obtention automatique de la clé, veuillez la récupérer manuellement",
|
||||
@@ -133,7 +175,8 @@
|
||||
"prev": "Précédent message",
|
||||
"top": "Retour en haut",
|
||||
"bottom": "Retour en bas",
|
||||
"close": "Fermer"
|
||||
"close": "Fermer",
|
||||
"history": "Historique des discussions"
|
||||
},
|
||||
"resend": "Réenvoyer",
|
||||
"save": "Enregistrer",
|
||||
@@ -176,7 +219,6 @@
|
||||
"topics.export.obsidian_created_placeholder": "Choisissez la date de création",
|
||||
"topics.export.obsidian_export_failed": "Échec de l'exportation",
|
||||
"topics.export.obsidian_export_success": "Exportation réussie",
|
||||
"topics.export.obsidian_not_configured": "Obsidian non configuré",
|
||||
"topics.export.obsidian_operate": "Mode de traitement",
|
||||
"topics.export.obsidian_operate_append": "Ajouter",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "Créer (écraser si existant)",
|
||||
@@ -203,7 +245,54 @@
|
||||
"topics.unpinned": "Annuler le fixage",
|
||||
"translate": "Traduire",
|
||||
"input.generate_image": "Générer une image",
|
||||
"input.generate_image_not_supported": "Le modèle ne supporte pas la génération d'images"
|
||||
"input.generate_image_not_supported": "Le modèle ne supporte pas la génération d'images",
|
||||
"history": {
|
||||
"assistant_node": "Assistant",
|
||||
"click_to_navigate": "Cliquez pour accéder au message correspondant",
|
||||
"coming_soon": "Le diagramme du flux de chat sera bientôt disponible",
|
||||
"no_messages": "Aucun message trouvé",
|
||||
"start_conversation": "Commencez une conversation pour visualiser le diagramme du flux de chat",
|
||||
"title": "Historique des chats",
|
||||
"user_node": "Utilisateur",
|
||||
"view_full_content": "Voir le contenu complet"
|
||||
},
|
||||
"input.translating": "Traduction en cours...",
|
||||
"input.thinking": "Pensée",
|
||||
"input.thinking.mode.default": "Défaut",
|
||||
"input.thinking.mode.default.tip": "Le modèle déterminera automatiquement le nombre de tokens à réfléchir",
|
||||
"input.thinking.mode.custom": "Personnalisé",
|
||||
"input.thinking.mode.custom.tip": "Nombre maximum de tokens sur lesquels le modèle peut réfléchir. Veuillez tenir compte des limites du contexte du modèle, sinon une erreur sera renvoyée",
|
||||
"input.thinking.budget_exceeds_max": "Le budget de réflexion dépasse le nombre maximum de tokens",
|
||||
"input.upload.upload_from_local": "Télécharger un fichier local...",
|
||||
"input.web_search.builtin": "Intégré au modèle",
|
||||
"input.web_search.builtin.enabled_content": "Utiliser la fonction de recherche web intégrée du modèle",
|
||||
"input.web_search.builtin.disabled_content": "Le modèle actuel ne prend pas en charge la recherche web",
|
||||
"input.web_search.no_web_search": "Pas de recherche web",
|
||||
"input.web_search.no_web_search.description": "Ne pas activer la fonction de recherche web",
|
||||
"settings.code_cacheable": "Mise en cache des blocs de code",
|
||||
"settings.code_cacheable.tip": "La mise en cache des blocs de code permet de réduire le temps de rendu des longs codes, mais augmente l'utilisation de la mémoire",
|
||||
"settings.code_cache_max_size": "Limite de cache",
|
||||
"settings.code_cache_max_size.tip": "Nombre maximal de caractères mis en cache (en milliers), calculé selon le code surligné. La taille du code surligné est beaucoup plus grande que celle du texte brut.",
|
||||
"settings.code_cache_ttl": "Durée du cache",
|
||||
"settings.code_cache_ttl.tip": "Temps d'expiration du cache (en minutes)",
|
||||
"settings.code_cache_threshold": "Seuil du cache",
|
||||
"settings.code_cache_threshold.tip": "Longueur minimale de code autorisée pour la mise en cache (en milliers de caractères). Seuls les blocs de code supérieurs à ce seuil seront mis en cache",
|
||||
"topics.export.md.reason": "Exporter au format Markdown (avec réflexion)",
|
||||
"topics.export.obsidian_vault": "Coffre-fort",
|
||||
"topics.export.obsidian_vault_placeholder": "Veuillez choisir un nom de coffre-fort",
|
||||
"topics.export.obsidian_path": "Chemin",
|
||||
"topics.export.obsidian_path_placeholder": "Veuillez choisir un chemin",
|
||||
"topics.export.obsidian_no_vaults": "Aucun coffre-fort Obsidian trouvé",
|
||||
"topics.export.obsidian_loading": "Chargement...",
|
||||
"topics.export.obsidian_fetch_error": "Échec de récupération du coffre-fort Obsidian",
|
||||
"topics.export.obsidian_fetch_folders_error": "Échec de récupération de la structure des dossiers",
|
||||
"topics.export.obsidian_no_vault_selected": "Veuillez d'abord sélectionner un coffre-fort",
|
||||
"topics.export.obsidian_select_vault_first": "Veuillez d'abord choisir un coffre-fort",
|
||||
"topics.export.obsidian_root_directory": "Répertoire racine",
|
||||
"topics.export.siyuan": "Exporter vers Siyuan Notes",
|
||||
"topics.export.wait_for_title_naming": "Génération du titre en cours...",
|
||||
"topics.export.title_naming_success": "Titre généré avec succès",
|
||||
"topics.export.title_naming_failed": "Échec de génération du titre, utilisation du titre par défaut"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Réduire",
|
||||
@@ -254,7 +343,16 @@
|
||||
"select": "Sélectionner",
|
||||
"topics": "Sujets",
|
||||
"warning": "Avertissement",
|
||||
"you": "Vous"
|
||||
"you": "Vous",
|
||||
"sort": {
|
||||
"pinyin": "Сортировать по пиньинь",
|
||||
"pinyin.asc": "Сортировать по пиньинь в порядке возрастания",
|
||||
"pinyin.desc": "Сортировать по пиньинь в порядке убывания"
|
||||
},
|
||||
"inspect": "Vérifier",
|
||||
"collapse": "Réduire",
|
||||
"loading": "Chargement...",
|
||||
"reasoning_content": "Réflexion approfondie"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentation d'aide"
|
||||
@@ -280,7 +378,9 @@
|
||||
"description": "La formule n'a pas été rendue avec succès, veuillez vérifier si le format de la formule est correct",
|
||||
"title": "Erreur de rendu"
|
||||
},
|
||||
"user_message_not_found": "Impossible de trouver le message d'utilisateur original"
|
||||
"user_message_not_found": "Impossible de trouver le message d'utilisateur original",
|
||||
"unknown": "Неизвестная ошибка",
|
||||
"pause_placeholder": "Прервано"
|
||||
},
|
||||
"export": {
|
||||
"assistant": "Assistant",
|
||||
@@ -393,7 +493,11 @@
|
||||
"topN_tooltip": "Nombre de résultats de correspondance retournés, plus le chiffre est élevé, plus il y a de résultats de correspondance, mais plus de jetons sont consommés",
|
||||
"url_added": "URL ajoutée",
|
||||
"url_placeholder": "Entrez l'URL, plusieurs URLs séparées par des sauts de ligne",
|
||||
"urls": "URLs"
|
||||
"urls": "URLs",
|
||||
"dimensions": "Размерность встраивания",
|
||||
"dimensions_size_tooltip": "Размерность встраивания. Чем больше значение, тем выше размерность, но тем больше токенов требуется",
|
||||
"dimensions_size_placeholder": "Значение по умолчанию (не рекомендуется изменять)",
|
||||
"dimensions_size_too_large": "Размерность встраивания не может превышать ограничение контекста модели ({{max_context}})"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Arabe",
|
||||
@@ -505,20 +609,63 @@
|
||||
"completed": "Terminé",
|
||||
"invoking": "En cours d'exécution",
|
||||
"raw": "Brut",
|
||||
"preview": "Aperçu"
|
||||
"preview": "Aperçu",
|
||||
"error": "Une erreur s'est produite"
|
||||
},
|
||||
"topic.added": "Thème ajouté avec succès",
|
||||
"upgrade.success.button": "Redémarrer",
|
||||
"upgrade.success.content": "Redémarrez pour finaliser la mise à jour",
|
||||
"upgrade.success.title": "Mise à jour réussie",
|
||||
"warn.notion.exporting": "Exportation en cours vers Notion, veuillez ne pas faire plusieurs demandes d'exportation!",
|
||||
"warning.rate.limit": "Vous envoyez trop souvent, veuillez attendre {{seconds}} secondes avant de réessayer"
|
||||
"warning.rate.limit": "Vous envoyez trop souvent, veuillez attendre {{seconds}} secondes avant de réessayer",
|
||||
"agents": {
|
||||
"imported": "Импортировано успешно",
|
||||
"import.error": "Ошибка импорта"
|
||||
},
|
||||
"citation": "{{count}} éléments cités",
|
||||
"error.invalid.nutstore": "Paramètres Nutstore invalides",
|
||||
"error.invalid.nutstore_token": "Jeton Nutstore invalide",
|
||||
"processing": "En cours de traitement...",
|
||||
"error.siyuan.export": "Échec de l'exportation de la note Siyuan, veuillez vérifier l'état de la connexion et la configuration indiquée dans le document",
|
||||
"error.siyuan.no_config": "L'adresse API ou le jeton Siyuan n'a pas été configuré",
|
||||
"success.siyuan.export": "Exportation vers Siyuan réussie",
|
||||
"warn.yuque.exporting": "Exportation Yuque en cours, veuillez ne pas demander à exporter à nouveau !",
|
||||
"warn.siyuan.exporting": "Exportation vers Siyuan en cours, veuillez ne pas demander à exporter à nouveau !",
|
||||
"download.success": "Téléchargement réussi",
|
||||
"download.failed": "Échec du téléchargement"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Ajouter à la barre latérale",
|
||||
"sidebar.remove.title": "Supprimer de la barre latérale",
|
||||
"sidebar.hide.title": "Masquer le mini-programme",
|
||||
"title": "Mini-programme"
|
||||
"title": "Mini-programme",
|
||||
"popup": {
|
||||
"refresh": "Обновить",
|
||||
"close": "Закрыть мини-программу",
|
||||
"minimize": "Свернуть мини-программу",
|
||||
"devtools": "Инструменты разработчика",
|
||||
"openExternal": "Открыть в браузере",
|
||||
"rightclick_copyurl": "Скопировать URL через правую кнопку мыши",
|
||||
"open_link_external_on": "Текущий: открывать ссылки в браузере",
|
||||
"open_link_external_off": "Текущий: открывать ссылки в окне по умолчанию"
|
||||
},
|
||||
"sidebar": {
|
||||
"add": {
|
||||
"title": "Ajouter à la barre latérale"
|
||||
},
|
||||
"remove": {
|
||||
"title": "Удалить из боковой панели"
|
||||
},
|
||||
"remove_custom": {
|
||||
"title": "Supprimer l'application personnalisée"
|
||||
},
|
||||
"hide": {
|
||||
"title": "Cacher"
|
||||
},
|
||||
"close": {
|
||||
"title": "Fermer"
|
||||
},
|
||||
"closeall": {
|
||||
"title": "Закрыть все"
|
||||
}
|
||||
}
|
||||
},
|
||||
"miniwindow": {
|
||||
"clipboard": {
|
||||
@@ -534,13 +681,17 @@
|
||||
"copy_last_message": "Appuyez sur C pour copier",
|
||||
"esc": "Appuyez sur ESC {{action}}",
|
||||
"esc_back": "Revenir en arrière",
|
||||
"esc_close": "Fermer la fenêtre"
|
||||
"esc_close": "Fermer la fenêtre",
|
||||
"backspace_clear": "Appuyez sur Retour arrière pour effacer"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": {
|
||||
"empty": "Demander à {{model}} pour obtenir de l'aide...",
|
||||
"title": "Que souhaitez-vous faire avec le texte ci-dessous"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"pin": "Закрепить окно"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
@@ -577,7 +728,9 @@
|
||||
"free": "Gratuit",
|
||||
"rerank": "Reclasser",
|
||||
"websearch": "Recherche web"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "Le modèle de réordonnancement ne prend pas en charge ce fournisseur ({{provider}}) pour le moment",
|
||||
"enable_tool_use": "Appel d'outil"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Agrandir la boîte de dialogue",
|
||||
@@ -609,7 +762,59 @@
|
||||
"regenerate.confirm": "Cela va remplacer les images générées, voulez-vous continuer?",
|
||||
"seed": "Graine aléatoire",
|
||||
"seed_tip": "La même graine et le même prompt peuvent générer des images similaires",
|
||||
"title": "Image"
|
||||
"title": "Image",
|
||||
"mode": {
|
||||
"generate": "Создать изображение",
|
||||
"edit": "Редактировать",
|
||||
"remix": "Смешать",
|
||||
"upscale": "Увеличить"
|
||||
},
|
||||
"generate": {
|
||||
"model_tip": "Версия модели: V2 — это последняя модель API, V2A — быстрая модель, V_1 — первое поколение модели, _TURBO — ускоренная версия",
|
||||
"number_images_tip": "Количество изображений за один раз",
|
||||
"seed_tip": "Контролирует случайность генерации изображения, используется для воспроизведения одинаковых результатов",
|
||||
"negative_prompt_tip": "Описывает элементы, которые вы не хотите видеть на изображении. Поддерживается только версиями V_1, V_1_TURBO, V_2 и V_2_TURBO",
|
||||
"magic_prompt_option_tip": "Интеллектуальная оптимизация подсказок для улучшения результатов генерации",
|
||||
"style_type_tip": "Стиль генерации изображения, применим к версии V_2 и выше"
|
||||
},
|
||||
"edit": {
|
||||
"image_file": "Image éditée",
|
||||
"model_tip": "L'édition partielle est uniquement prise en charge par les versions V_2 et V_2_TURBO",
|
||||
"number_images_tip": "Nombre de résultats d'édition générés",
|
||||
"style_type_tip": "Style de l'image après édition, uniquement applicable aux versions V_2 et ultérieures",
|
||||
"seed_tip": "Contrôle la variabilité aléatoire des résultats d'édition",
|
||||
"magic_prompt_option_tip": "Optimisation intelligente du mot-clé d'édition"
|
||||
},
|
||||
"remix": {
|
||||
"model_tip": "Sélectionnez la version du modèle IA à utiliser pour le remix",
|
||||
"image_file": "Image de référence",
|
||||
"image_weight": "Poids de l'image de référence",
|
||||
"image_weight_tip": "Ajustez l'influence de l'image de référence",
|
||||
"number_images_tip": "Nombre de résultats de remix à générer",
|
||||
"seed_tip": "Contrôle l'aléatoire des résultats de remix",
|
||||
"style_type_tip": "Style de l'image après le remix, uniquement applicable aux versions V_2 et supérieures",
|
||||
"negative_prompt_tip": "Décrivez les éléments que vous ne souhaitez pas voir apparaître dans le résultat du remix",
|
||||
"magic_prompt_option_tip": "Optimisation intelligente des mots-clés du remix"
|
||||
},
|
||||
"upscale": {
|
||||
"image_file": "Image à agrandir",
|
||||
"resemblance": "Similarité",
|
||||
"resemblance_tip": "Contrôle le niveau de similarité entre le résultat agrandi et l'image originale",
|
||||
"detail": "Détail",
|
||||
"detail_tip": "Contrôle l'intensité de l'amélioration des détails dans l'image agrandie",
|
||||
"number_images_tip": "Nombre de résultats d'agrandissement générés",
|
||||
"seed_tip": "Contrôle la randomisation du résultat d'agrandissement",
|
||||
"magic_prompt_option_tip": "Optimisation intelligente du prompt d'agrandissement"
|
||||
},
|
||||
"magic_prompt_option": "Amélioration du prompt",
|
||||
"model": "Version",
|
||||
"aspect_ratio": "Format d'image",
|
||||
"style_type": "Style",
|
||||
"learn_more": "En savoir plus",
|
||||
"prompt_placeholder_edit": "Entrez votre description d'image, utilisez des guillemets « \"\" » pour le texte à dessiner",
|
||||
"proxy_required": "Actuellement, un proxy doit être activé pour afficher les images générées. Le support pour une connexion directe depuis la Chine sera ajouté ultérieurement.",
|
||||
"image_file_required": "Veuillez d'abord télécharger une image",
|
||||
"image_file_retry": "Veuillez réuploader l'image"
|
||||
},
|
||||
"plantuml": {
|
||||
"download": {
|
||||
@@ -672,7 +877,9 @@
|
||||
"xirang": "CTyun XiRang",
|
||||
"yi": "ZéroUnInfini",
|
||||
"zhinao": "360 ZhiNao",
|
||||
"zhipu": "ZhiPu IA"
|
||||
"zhipu": "ZhiPu IA",
|
||||
"voyageai": "Voyage AI",
|
||||
"qiniu": "Qiniu Cloud"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "Êtes-vous sûr de vouloir restaurer les données ?",
|
||||
@@ -714,7 +921,6 @@
|
||||
"advanced.title": "Paramètres avancés",
|
||||
"assistant": "Assistant par défaut",
|
||||
"assistant.model_params": "Paramètres du modèle",
|
||||
"assistant.show.icon": "Afficher l'icône du modèle",
|
||||
"assistant.title": "Assistant par défaut",
|
||||
"data": {
|
||||
"app_data": "Données de l'application",
|
||||
@@ -780,13 +986,13 @@
|
||||
"notion.split_size_placeholder": "Veuillez entrer la limite de blocs par page (par défaut 90)",
|
||||
"notion.title": "Configuration Notion",
|
||||
"obsidian": {
|
||||
"folder": "Dossier",
|
||||
"folder_placeholder": "Veuillez entrer le nom du dossier",
|
||||
"tags": "Étiquettes globales",
|
||||
"tags_placeholder": "Veuillez entrer le nom de l'étiquette, plusieurs étiquettes sont séparées par une virgule anglaise, Obsidian ne peut pas utiliser uniquement des chiffres",
|
||||
"title": "Configuration d'Obsidian",
|
||||
"vault": "Coffre-fort",
|
||||
"vault_placeholder": "Veuillez entrer le nom du coffre-fort"
|
||||
"default_vault": "Référentiel Obsidian par défaut",
|
||||
"default_vault_placeholder": "Veuillez sélectionner un référentiel Obsidian par défaut",
|
||||
"default_vault_loading": "Récupération du référentiel Obsidian en cours...",
|
||||
"default_vault_no_vaults": "Aucun référentiel Obsidian trouvé",
|
||||
"default_vault_fetch_error": "Échec de la récupération du référentiel Obsidian",
|
||||
"default_vault_export_failed": "Échec de l'exportation"
|
||||
},
|
||||
"title": "Paramètres des données",
|
||||
"webdav": {
|
||||
@@ -818,7 +1024,26 @@
|
||||
"title": "WebDAV",
|
||||
"user": "Nom d'utilisateur WebDAV",
|
||||
"maxBackups": "Nombre maximal de sauvegardes",
|
||||
"maxBackups.unlimited": "Illimité"
|
||||
"maxBackups.unlimited": "Illimité",
|
||||
"backup.manager.title": "Gestion des sauvegardes",
|
||||
"backup.manager.refresh": "Actualiser",
|
||||
"backup.manager.delete.selected": "Supprimer la sélection",
|
||||
"backup.manager.delete.text": "Supprimer",
|
||||
"backup.manager.restore.text": "Restaurer",
|
||||
"backup.manager.restore.success": "Restauration réussie, l'application sera actualisée dans quelques secondes",
|
||||
"backup.manager.restore.error": "Échec de la restauration",
|
||||
"backup.manager.delete.confirm.title": "Confirmer la suppression",
|
||||
"backup.manager.delete.confirm.single": "Voulez-vous vraiment supprimer le fichier de sauvegarde \"{{fileName}}\" ? Cette action est irréversible.",
|
||||
"backup.manager.delete.confirm.multiple": "Voulez-vous vraiment supprimer les {{count}} fichiers de sauvegarde sélectionnés ? Cette action est irréversible.",
|
||||
"backup.manager.delete.success.single": "Suppression réussie",
|
||||
"backup.manager.delete.success.multiple": "{{count}} fichiers de sauvegarde supprimés avec succès",
|
||||
"backup.manager.delete.error": "Échec de la suppression",
|
||||
"backup.manager.fetch.error": "Échec de la récupération des fichiers de sauvegarde",
|
||||
"backup.manager.select.files.delete": "Veuillez sélectionner les fichiers de sauvegarde à supprimer",
|
||||
"backup.manager.columns.fileName": "Nom du fichier",
|
||||
"backup.manager.columns.modifiedTime": "Date de modification",
|
||||
"backup.manager.columns.size": "Taille",
|
||||
"backup.manager.columns.actions": "Actions"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -834,16 +1059,73 @@
|
||||
"title": "Configuration Yuque",
|
||||
"token": "Token Yuque",
|
||||
"token_placeholder": "Veuillez entrer le Token Yuque"
|
||||
}
|
||||
},
|
||||
"export_menu": {
|
||||
"title": "Exporter les paramètres du menu",
|
||||
"image": "Exporter en tant qu'image",
|
||||
"markdown": "Exporter au format Markdown",
|
||||
"markdown_reason": "Exporter au format Markdown (avec réflexion incluse)",
|
||||
"notion": "Exporter vers Notion",
|
||||
"yuque": "Exporter vers Yuque",
|
||||
"obsidian": "Exporter vers Obsidian",
|
||||
"siyuan": "Exporter vers Siyuan Notes",
|
||||
"joplin": "Exporter vers Joplin",
|
||||
"docx": "Exporter au format Word"
|
||||
},
|
||||
"siyuan": {
|
||||
"check": {
|
||||
"title": "Проверка подключения",
|
||||
"button": "Проверить",
|
||||
"empty_config": "Пожалуйста, введите адрес API и токен",
|
||||
"success": "Подключение успешно",
|
||||
"fail": "Не удалось подключиться, проверьте адрес API и токен",
|
||||
"error": "Аномалия подключения, проверьте сетевое соединение"
|
||||
},
|
||||
"title": "Настройка CherryNote",
|
||||
"api_url": "Адрес API",
|
||||
"api_url_placeholder": "Например: http://127.0.0.1:6806",
|
||||
"token": "Токен API",
|
||||
"token.help": "Получить в разделе CherryNote -> Настройки -> О программе",
|
||||
"token_placeholder": "Введите токен CherryNote",
|
||||
"box_id": "Идентификатор блокнота",
|
||||
"box_id_placeholder": "Введите идентификатор блокнота",
|
||||
"root_path": "Корневой путь документа",
|
||||
"root_path_placeholder": "Например: /CherryStudio"
|
||||
},
|
||||
"nutstore": {
|
||||
"title": "Настройка坚果云",
|
||||
"isLogin": "Вход выполнен",
|
||||
"notLogin": "Вход не выполнен",
|
||||
"login.button": "Войти",
|
||||
"logout.button": "Выйти из аккаунта",
|
||||
"logout.title": "Вы действительно хотите выйти из аккаунта坚果云?",
|
||||
"logout.content": "После выхода будет невозможно создать резервную копию в坚果云 или восстановить данные из нее",
|
||||
"checkConnection.name": "Проверить соединение",
|
||||
"checkConnection.success": "Соединение с坚果云 установлено",
|
||||
"checkConnection.fail": "Не удалось подключиться к坚果云",
|
||||
"username": "Имя пользователя坚果云",
|
||||
"path": "Путь хранения данных坚果云",
|
||||
"path.placeholder": "Введите путь хранения данных坚果云",
|
||||
"backup.button": "Резервное копирование в坚果云",
|
||||
"restore.button": "Восстановление из坚果云",
|
||||
"pathSelector.title": "Путь хранения данных坚果云",
|
||||
"pathSelector.return": "Назад",
|
||||
"pathSelector.currentPath": "Текущий путь",
|
||||
"new_folder.button.confirm": "Подтвердить",
|
||||
"new_folder.button.cancel": "Отмена",
|
||||
"new_folder.button": "Создать папку"
|
||||
},
|
||||
"divider.basic": "Paramètres de base",
|
||||
"divider.cloud_storage": "Paramètres de sauvegarde cloud",
|
||||
"divider.export_settings": "Paramètres d'exportation",
|
||||
"divider.third_party": "Connexion tierce",
|
||||
"message_title.use_topic_naming.title": "Utiliser le modèle de dénomination thématique pour créer les titres des messages exportés",
|
||||
"message_title.use_topic_naming.help": "Lorsque cette option est activée, le modèle de dénomination thématique sera utilisé pour créer les titres des messages exportés. Cette option affectera également toutes les méthodes d'exportation au format Markdown."
|
||||
},
|
||||
"display.assistant.title": "Paramètres de l'assistant",
|
||||
"display.custom.css": "CSS personnalisé",
|
||||
"display.custom.css.cherrycss": "Obtenir depuis cherrycss.com",
|
||||
"display.custom.css.placeholder": "/* Écrire votre CSS personnalisé ici */",
|
||||
"display.minApp.disabled": "Applications minimisées masquées",
|
||||
"display.minApp.empty": "Glissez les applications minimisées à masquer ici",
|
||||
"display.minApp.title": "Paramètres d'affichage des applications minimisées",
|
||||
"display.minApp.visible": "Applications minimisées affichées",
|
||||
"display.sidebar.chat.hiddenMessage": "L'assistant est une fonction de base et ne peut pas être masquée",
|
||||
"display.sidebar.disabled": "Icônes masquées",
|
||||
"display.sidebar.empty": "Glissez les fonctions à masquer ici",
|
||||
@@ -864,7 +1146,6 @@
|
||||
"general.display.title": "Paramètres d'affichage",
|
||||
"general.emoji_picker": "Sélectionneur d'émoticônes",
|
||||
"general.image_upload": "Téléchargement d'images",
|
||||
"general.manually_check_update.title": "Désactiver la vérification des mises à jour",
|
||||
"general.reset.button": "Réinitialiser",
|
||||
"general.reset.title": "Réinitialiser les données",
|
||||
"general.restore.button": "Restaurer",
|
||||
@@ -930,11 +1211,94 @@
|
||||
"serverPlural": "Serveurs",
|
||||
"serverSingular": "Serveur",
|
||||
"title": "Serveurs MCP",
|
||||
"toggleError": "Échec du basculement",
|
||||
"type": "Type",
|
||||
"updateError": "Échec de la mise à jour du serveur",
|
||||
"updateSuccess": "Serveur mis à jour avec succès",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"errors": {
|
||||
"32000": "Échec du démarrage du serveur MCP, veuillez vérifier si tous les paramètres sont correctement remplis conformément au tutoriel"
|
||||
},
|
||||
"tabs": {
|
||||
"general": "Général",
|
||||
"description": "Description",
|
||||
"tools": "Outils",
|
||||
"prompts": "Prompts",
|
||||
"resources": "Ressources"
|
||||
},
|
||||
"tools": {
|
||||
"inputSchema": "Schéma d'entrée",
|
||||
"availableTools": "Outils disponibles",
|
||||
"noToolsAvailable": "Aucun outil disponible",
|
||||
"loadError": "Échec de la récupération des outils"
|
||||
},
|
||||
"prompts": {
|
||||
"availablePrompts": "Invites disponibles",
|
||||
"noPromptsAvailable": "Aucune invite disponible",
|
||||
"arguments": "Arguments",
|
||||
"requiredField": "Champ obligatoire",
|
||||
"genericError": "Erreur lors de la récupération des invites",
|
||||
"loadError": "Échec de la récupération des invites"
|
||||
},
|
||||
"resources": {
|
||||
"noResourcesAvailable": "Нет доступных ресурсов",
|
||||
"availableResources": "Доступные ресурсы",
|
||||
"uri": "URI",
|
||||
"mimeType": "Тип MIME",
|
||||
"size": "Размер",
|
||||
"blob": "Бинарные данные",
|
||||
"blobInvisible": "Скрытые бинарные данные",
|
||||
"text": "Текст"
|
||||
},
|
||||
"types": {
|
||||
"inMemory": "Intégré",
|
||||
"sse": "SSE",
|
||||
"streamableHttp": "Flux continu",
|
||||
"stdio": "STDIO"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Синхронизация сервера",
|
||||
"selectProvider": "Выберите провайдера:",
|
||||
"discoverMcpServers": "Обнаружить MCP-серверы",
|
||||
"discoverMcpServersDescription": "Посетите платформу для обнаружения доступных MCP-серверов",
|
||||
"getToken": "Получить API-токен",
|
||||
"getTokenDescription": "Получите персональный API-токен из вашей учетной записи",
|
||||
"setToken": "Введите ваш токен",
|
||||
"tokenRequired": "Требуется API-токен",
|
||||
"tokenPlaceholder": "Введите API-токен здесь",
|
||||
"button": "Синхронизировать",
|
||||
"error": "Ошибка синхронизации MCP-сервера",
|
||||
"success": "MCP-сервер успешно синхронизирован",
|
||||
"unauthorized": "Синхронизация не авторизована",
|
||||
"noServersAvailable": "Нет доступных MCP-серверов"
|
||||
},
|
||||
"sse": "Серверные отправляемые события (sse)",
|
||||
"streamableHttp": "HTTP поддерживающий потоковую передачу (streamableHttp)",
|
||||
"stdio": "Стандартный ввод/вывод (stdio)",
|
||||
"inMemory": "В памяти",
|
||||
"headers": "Заголовки запроса",
|
||||
"headersTooltip": "Пользовательские заголовки HTTP-запроса",
|
||||
"searchNpx": "Поиск MCP",
|
||||
"newServer": "Сервер MCP",
|
||||
"startError": "Ошибка запуска",
|
||||
"editMcpJson": "Редактировать конфигурацию MCP",
|
||||
"installHelp": "Получить помощь по установке",
|
||||
"deleteServer": "Удалить сервер",
|
||||
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?",
|
||||
"registry": "Источник управления пакетами",
|
||||
"registryTooltip": "Выберите источник для установки пакетов, чтобы решить проблемы с сетью по умолчанию.",
|
||||
"registryDefault": "По умолчанию",
|
||||
"not_support": "Модель не поддерживается",
|
||||
"user": "Пользователь",
|
||||
"system": "Система",
|
||||
"timeout": "Таймаут",
|
||||
"timeoutTooltip": "Таймаут запроса к серверу (в секундах), по умолчанию 60 секунд",
|
||||
"provider": "Поставщик",
|
||||
"providerUrl": "Адрес поставщика",
|
||||
"logoUrl": "Адрес логотипа",
|
||||
"tags": "Теги",
|
||||
"tagsPlaceholder": "Введите теги",
|
||||
"providerPlaceholder": "Название поставщика",
|
||||
"advancedSettings": "Расширенные настройки"
|
||||
},
|
||||
"messages.divider": "Séparateur de messages",
|
||||
"messages.grid_columns": "Nombre de colonnes de la grille de messages",
|
||||
@@ -1046,13 +1410,29 @@
|
||||
"docs_more_details": "Obtenir plus de détails",
|
||||
"get_api_key": "Cliquez ici pour obtenir une clé",
|
||||
"is_not_support_array_content": "Activer le mode compatible",
|
||||
"no_models": "Veuillez ajouter un modèle avant de vérifier la connexion API",
|
||||
"not_checked": "Non vérifié",
|
||||
"remove_duplicate_keys": "Supprimer les clés en double",
|
||||
"remove_invalid_keys": "Supprimer les clés invalides",
|
||||
"search": "Rechercher une plateforme de modèles...",
|
||||
"search_placeholder": "Rechercher un ID ou un nom de modèle",
|
||||
"title": "Services de modèles"
|
||||
"title": "Services de modèles",
|
||||
"oauth": {
|
||||
"button": "Войти через аккаунт {{provider}}",
|
||||
"description": "Этот сервис предоставляется <website>{{provider}}</website>",
|
||||
"official_website": "Официальный сайт"
|
||||
},
|
||||
"notes": {
|
||||
"title": "Примечание к модели",
|
||||
"placeholder": "Введите содержимое в формате Markdown...",
|
||||
"markdown_editor_default_value": "Область предварительного просмотра"
|
||||
},
|
||||
"basic_auth": "Authentification HTTP",
|
||||
"basic_auth.tip": "S'applique aux instances déployées via le serveur (voir la documentation). Seule la méthode Basic est actuellement prise en charge (RFC7617).",
|
||||
"basic_auth.user_name": "Nom d'utilisateur",
|
||||
"basic_auth.user_name.tip": "Laisser vide pour désactiver",
|
||||
"basic_auth.password": "Mot de passe",
|
||||
"bills": "Factures",
|
||||
"no_models_for_check": "Aucun modèle détectable (par exemple, modèle de chat)"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1129,8 +1509,111 @@
|
||||
"description": "Tavily est un moteur de recherche conçu spécifiquement pour les agents IA, offrant des résultats en temps réel, précis, des suggestions de requêtes intelligentes et des capacités de recherche approfondie",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Recherche sur Internet"
|
||||
}
|
||||
"title": "Recherche sur Internet",
|
||||
"overwrite": "Remplacer la recherche du fournisseur",
|
||||
"overwrite_tooltip": "Forcer l'utilisation du moteur de recherche du fournisseur au lieu du modèle linguistique volumineux",
|
||||
"subscribe": "Abonnement à la liste noire",
|
||||
"subscribe_update": "Mettre à jour maintenant",
|
||||
"subscribe_add": "Ajouter un abonnement",
|
||||
"subscribe_url": "Adresse de la source d'abonnement",
|
||||
"subscribe_name": "Nom alternatif",
|
||||
"subscribe_name.placeholder": "Nom alternatif utilisé lorsque la source d'abonnement téléchargée ne contient pas de nom",
|
||||
"subscribe_add_success": "Source d'abonnement ajoutée avec succès !",
|
||||
"subscribe_delete": "Supprimer la source d'abonnement",
|
||||
"apikey": "Clé API",
|
||||
"free": "Gratuit",
|
||||
"content_limit": "Limite de longueur du contenu",
|
||||
"content_limit_tooltip": "Limite la longueur du contenu des résultats de recherche, le contenu dépassant la limite sera tronqué"
|
||||
},
|
||||
"miniapps": {
|
||||
"open_link_external": {
|
||||
"title": "Ouvrir un nouveau lien dans une fenêtre du navigateur"
|
||||
},
|
||||
"custom": {
|
||||
"title": "Пользовательское приложение",
|
||||
"edit_title": "Редактировать пользовательское приложение",
|
||||
"save_success": "Пользовательское приложение успешно сохранено.",
|
||||
"save_error": "Не удалось сохранить пользовательское приложение.",
|
||||
"remove_success": "Пользовательское приложение успешно удалено.",
|
||||
"remove_error": "Не удалось удалить пользовательское приложение.",
|
||||
"logo_upload_success": "Логотип успешно загружен.",
|
||||
"logo_upload_error": "Не удалось загрузить логотип.",
|
||||
"id": "ID",
|
||||
"id_error": "Поле ID обязательно для заполнения.",
|
||||
"id_placeholder": "Введите ID",
|
||||
"name": "Имя",
|
||||
"name_error": "Поле Имя обязательно для заполнения.",
|
||||
"name_placeholder": "Введите имя",
|
||||
"url": "URL",
|
||||
"url_error": "Поле URL обязательно для заполнения.",
|
||||
"url_placeholder": "Введите URL",
|
||||
"logo": "Логотип",
|
||||
"logo_url": "URL логотипа",
|
||||
"logo_file": "Загрузить файл логотипа",
|
||||
"logo_url_label": "URL логотипа",
|
||||
"logo_url_placeholder": "Введите URL логотипа",
|
||||
"logo_upload_label": "Загрузить логотип",
|
||||
"logo_upload_button": "Загрузить",
|
||||
"save": "Сохранить",
|
||||
"edit_description": "Здесь вы можете отредактировать конфигурацию пользовательского приложения. Каждое приложение должно содержать поля id, name, url и logo.",
|
||||
"placeholder": "Введите конфигурацию пользовательского приложения (в формате JSON)",
|
||||
"duplicate_ids": "Обнаружены повторяющиеся ID: {{ids}}",
|
||||
"conflicting_ids": "Конфликтующие ID с ID по умолчанию: {{ids}}"
|
||||
},
|
||||
"title": "Paramètres de l'application",
|
||||
"disabled": "Applications masquées",
|
||||
"empty": "Faites glisser vers ici les applications que vous souhaitez masquer",
|
||||
"visible": "Applications visibles",
|
||||
"cache_settings": "Paramètres du cache",
|
||||
"cache_title": "Nombre de caches d'applications",
|
||||
"cache_description": "Définir le nombre maximum d'applications pouvant rester actives simultanément",
|
||||
"reset_tooltip": "Réinitialiser aux valeurs par défaut",
|
||||
"display_title": "Paramètres d'affichage des applications",
|
||||
"sidebar_title": "Affichage des applications actives dans la barre latérale",
|
||||
"sidebar_description": "Définir si les applications actives doivent s'afficher dans la barre latérale",
|
||||
"cache_change_notice": "Les modifications prendront effet après l'ajout ou la suppression d'applications ouvertes jusqu'à atteindre la valeur définie"
|
||||
},
|
||||
"quickPhrase": {
|
||||
"title": "Быстрые фразы",
|
||||
"add": "Добавить фразу",
|
||||
"edit": "Редактировать фразу",
|
||||
"titleLabel": "Заголовок",
|
||||
"contentLabel": "Содержание",
|
||||
"titlePlaceholder": "Введите заголовок фразы",
|
||||
"contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, после этого нажмите Tab, чтобы быстро перейти к переменной для редактирования. Например: \\n Запланируй маршрут от ${from} до ${to}, а затем отправь его на ${email}.",
|
||||
"delete": "Удалить фразу",
|
||||
"deleteConfirm": "После удаления фразы её невозможно восстановить. Продолжить?",
|
||||
"locationLabel": "Добавить местоположение",
|
||||
"global": "Глобальные фразы",
|
||||
"assistant": "Фразы помощника"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Быстрое меню",
|
||||
"close": "Закрыть",
|
||||
"select": "Выбрать",
|
||||
"page": "Перелистнуть страницу",
|
||||
"confirm": "Подтвердить",
|
||||
"back": "Назад",
|
||||
"forward": "Вперед",
|
||||
"multiple": "Множественный выбор"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Настройки конфиденциальности",
|
||||
"enable_privacy_mode": "Отправлять анонимные сообщения об ошибках и статистику"
|
||||
},
|
||||
"assistant.icon.type": "Type d'icône du modèle",
|
||||
"assistant.icon.type.model": "Icône de modèle",
|
||||
"assistant.icon.type.emoji": "Emoji",
|
||||
"assistant.icon.type.none": "Ne pas afficher",
|
||||
"general.auto_check_update.title": "Mise à jour automatique",
|
||||
"input.show_translate_confirm": "Afficher la boîte de dialogue de confirmation de traduction",
|
||||
"messages.prompt": "Mot-clé d'affichage",
|
||||
"messages.input.enable_quick_triggers": "Activer les menus rapides avec '/' et '@'",
|
||||
"messages.input.enable_delete_model": "Activer la touche Supprimer pour effacer le modèle/pièce jointe saisie",
|
||||
"messages.math_engine.none": "Aucun",
|
||||
"models.manage.add_listed": "Ajouter un modèle depuis la liste",
|
||||
"models.manage.remove_listed": "Supprimer un modèle de la liste",
|
||||
"zoom.title": "Zoom de la page"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "langue arbitraire",
|
||||
@@ -1155,7 +1638,10 @@
|
||||
"scroll_sync.disable": "désactiver la synchronisation du défilement",
|
||||
"scroll_sync.enable": "activer la synchronisation du défilement",
|
||||
"title": "traduction",
|
||||
"tooltip.newline": "saut de ligne"
|
||||
"tooltip.newline": "saut de ligne",
|
||||
"menu": {
|
||||
"description": "Traduire le contenu de la zone de saisie actuelle"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"quit": "Quitter",
|
||||
@@ -1169,4 +1655,4 @@
|
||||
"visualization": "Visualisation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,26 @@
|
||||
"tag.default": "Padrão",
|
||||
"tag.new": "Novo",
|
||||
"tag.system": "Sistema",
|
||||
"title": "Agente"
|
||||
"title": "Agente",
|
||||
"import": {
|
||||
"type": {
|
||||
"url": "URL",
|
||||
"file": "Arquivo"
|
||||
},
|
||||
"error": {
|
||||
"url_required": "Por favor, insira a URL",
|
||||
"fetch_failed": "Falha ao buscar dados da URL",
|
||||
"invalid_format": "Formato de proxy inválido: campos obrigatórios ausentes"
|
||||
},
|
||||
"title": "Importar do exterior",
|
||||
"url_placeholder": "Insira o URL JSON",
|
||||
"select_file": "Selecionar arquivo",
|
||||
"button": "Importar",
|
||||
"file_filter": "Arquivo JSON"
|
||||
},
|
||||
"export": {
|
||||
"agent": "Exportar Agente"
|
||||
}
|
||||
},
|
||||
"assistants": {
|
||||
"abbr": "Assistente",
|
||||
@@ -52,8 +71,31 @@
|
||||
"settings.reasoning_effort.low": "Curto",
|
||||
"settings.reasoning_effort.medium": "Médio",
|
||||
"settings.reasoning_effort.off": "Desligado",
|
||||
"settings.reasoning_effort.tip": "Apenas suporta modelos de raciocínio OpenAI o-series e Anthropic",
|
||||
"title": "Assistente"
|
||||
"title": "Assistente",
|
||||
"settings.regular_phrases": {
|
||||
"title": "Frases Comuns",
|
||||
"add": "Adicionar Frase",
|
||||
"edit": "Editar Frase",
|
||||
"delete": "Excluir Frase",
|
||||
"deleteConfirm": "Tem certeza de que deseja excluir esta frase?",
|
||||
"titleLabel": "Título",
|
||||
"titlePlaceholder": "Digite o título",
|
||||
"contentLabel": "Conteúdo",
|
||||
"contentPlaceholder": "Por favor, insira o conteúdo da frase. Há suporte para o uso de variáveis, e em seguida você pode pressionar a tecla Tab para localizar rapidamente a variável e editá-la. Por exemplo:\\n Planeie uma rota de ${from} para ${to} e depois envie para ${email}."
|
||||
},
|
||||
"settings.title": "Configurações do Assistente",
|
||||
"icon.type": "Ícone do Assistente",
|
||||
"settings.mcp": "Servidor MCP",
|
||||
"settings.mcp.enableFirst": "Por favor, ative este servidor nas configurações do MCP primeiro",
|
||||
"settings.mcp.title": "Configurações do MCP",
|
||||
"settings.mcp.noServersAvailable": "Nenhum servidor MCP disponível. Adicione um servidor nas configurações",
|
||||
"settings.mcp.description": "Servidor MCP ativado por padrão",
|
||||
"settings.knowledge_base.recognition.tip": "O agente usará a capacidade de reconhecimento de intenção do grande modelo para decidir se deve chamar a base de conhecimento para responder. Esta função depende da capacidade do modelo",
|
||||
"settings.knowledge_base.recognition": "Chamar base de conhecimento",
|
||||
"settings.knowledge_base.recognition.off": "Busca forçada",
|
||||
"settings.knowledge_base.recognition.on": "Reconhecimento de intenção",
|
||||
"settings.reasoning_effort.default": "Padrão",
|
||||
"settings.more": "Configurações do Assistente"
|
||||
},
|
||||
"auth": {
|
||||
"error": "Falha ao obter a chave automaticamente, por favor obtenha manualmente",
|
||||
@@ -133,7 +175,8 @@
|
||||
"prev": "Mensagem anterior",
|
||||
"top": "Voltar ao topo",
|
||||
"bottom": "Voltar ao fundo",
|
||||
"close": "Fechar"
|
||||
"close": "Fechar",
|
||||
"history": "Histórico de Conversas"
|
||||
},
|
||||
"resend": "Reenviar",
|
||||
"save": "Salvar",
|
||||
@@ -176,7 +219,6 @@
|
||||
"topics.export.obsidian_created_placeholder": "Selecione a data de criação",
|
||||
"topics.export.obsidian_export_failed": "Exportação falhou",
|
||||
"topics.export.obsidian_export_success": "Exportação bem-sucedida",
|
||||
"topics.export.obsidian_not_configured": "Obsidian não configurado",
|
||||
"topics.export.obsidian_operate": "Operação",
|
||||
"topics.export.obsidian_operate_append": "Anexar",
|
||||
"topics.export.obsidian_operate_new_or_overwrite": "Criar novo (substituir se existir)",
|
||||
@@ -203,7 +245,55 @@
|
||||
"topics.unpinned": "Desfixar",
|
||||
"translate": "Traduzir",
|
||||
"input.generate_image": "Gerar imagem",
|
||||
"input.generate_image_not_supported": "Modelo não suporta geração de imagem"
|
||||
"input.generate_image_not_supported": "Modelo não suporta geração de imagem",
|
||||
"history": {
|
||||
"assistant_node": "Assistente",
|
||||
"click_to_navigate": "Clique para pular para a mensagem correspondente",
|
||||
"coming_soon": "O gráfico do fluxo de chat estará disponível em breve",
|
||||
"no_messages": "Nenhuma mensagem encontrada",
|
||||
"start_conversation": "Inicie uma conversa para visualizar o gráfico do fluxo de chat",
|
||||
"title": "Histórico de Chat",
|
||||
"user_node": "Usuário",
|
||||
"view_full_content": "Ver conteúdo completo"
|
||||
},
|
||||
"input.translating": "Traduzindo...",
|
||||
"input.thinking": "Pensando",
|
||||
"input.thinking.mode.default": "Padrão",
|
||||
"input.thinking.mode.default.tip": "O modelo determinará automaticamente o número de tokens a serem pensados",
|
||||
"input.thinking.mode.custom": "Personalizado",
|
||||
"input.thinking.mode.custom.tip": "Número máximo de tokens que o modelo pode utilizar para pensar. Considere os limites de contexto do modelo, caso contrário ocorrerá um erro",
|
||||
"input.thinking.mode.tokens.tip": "Definir o número de tokens para raciocínio",
|
||||
"input.thinking.budget_exceeds_max": "Orçamento de pensamento excede o número máximo de tokens",
|
||||
"input.upload.upload_from_local": "Fazer upload de arquivo local...",
|
||||
"input.web_search.builtin": "Integrado ao modelo",
|
||||
"input.web_search.builtin.enabled_content": "Usar a função integrada de busca na web do modelo",
|
||||
"input.web_search.builtin.disabled_content": "Este modelo não suporta busca na web",
|
||||
"input.web_search.no_web_search": "Sem busca na web",
|
||||
"input.web_search.no_web_search.description": "Não ativar a função de busca na web",
|
||||
"settings.code_cacheable": "Cache de blocos de código",
|
||||
"settings.code_cacheable.tip": "O cache de blocos de código reduz o tempo de renderização de códigos longos, mas aumenta o uso de memória",
|
||||
"settings.code_cache_max_size": "Limite do cache",
|
||||
"settings.code_cache_max_size.tip": "Limite de caracteres permitidos no cache (em milhares de caracteres), calculado com base no código com sintaxe destacada. O código destacado é significativamente maior que texto puro.",
|
||||
"settings.code_cache_ttl": "Tempo de vida do cache",
|
||||
"settings.code_cache_ttl.tip": "Tempo em minutos até o cache expirar",
|
||||
"settings.code_cache_threshold": "Limiar para cache",
|
||||
"settings.code_cache_threshold.tip": "Tamanho mínimo de código permitido para cache (em milhares de caracteres). Apenas blocos maiores que esse limiar serão armazenados em cache",
|
||||
"topics.export.md.reason": "Exportar como Markdown (incluindo raciocínios)",
|
||||
"topics.export.obsidian_vault": "Cofre",
|
||||
"topics.export.obsidian_vault_placeholder": "Selecione o nome do cofre",
|
||||
"topics.export.obsidian_path": "Caminho",
|
||||
"topics.export.obsidian_path_placeholder": "Selecione o caminho",
|
||||
"topics.export.obsidian_no_vaults": "Nenhum cofre Obsidian encontrado",
|
||||
"topics.export.obsidian_loading": "Carregando...",
|
||||
"topics.export.obsidian_fetch_error": "Falha ao carregar cofres Obsidian",
|
||||
"topics.export.obsidian_fetch_folders_error": "Falha ao carregar estrutura de pastas",
|
||||
"topics.export.obsidian_no_vault_selected": "Por favor, selecione um cofre primeiro",
|
||||
"topics.export.obsidian_select_vault_first": "Por favor, selecione um cofre primeiro",
|
||||
"topics.export.obsidian_root_directory": "Diretório raiz",
|
||||
"topics.export.siyuan": "Exportar para a nota Siyuan",
|
||||
"topics.export.wait_for_title_naming": "Gerando título...",
|
||||
"topics.export.title_naming_success": "Título gerado com sucesso",
|
||||
"topics.export.title_naming_failed": "Falha ao gerar título, usando título padrão"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Recolher",
|
||||
@@ -254,7 +344,16 @@
|
||||
"select": "Selecionar",
|
||||
"topics": "Tópicos",
|
||||
"warning": "Aviso",
|
||||
"you": "Você"
|
||||
"you": "Você",
|
||||
"sort": {
|
||||
"pinyin": "Ordenar por Pinyin",
|
||||
"pinyin.asc": "Ordenar por Pinyin em ordem crescente",
|
||||
"pinyin.desc": "Ordenar por Pinyin em ordem decrescente"
|
||||
},
|
||||
"inspect": "Verificar",
|
||||
"collapse": "Recolher",
|
||||
"loading": "Carregando...",
|
||||
"reasoning_content": "Pensamento profundo concluído"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentação de Ajuda"
|
||||
@@ -280,7 +379,9 @@
|
||||
"description": "Falha ao renderizar a fórmula, por favor verifique se o formato da fórmula está correto",
|
||||
"title": "Erro de Renderização"
|
||||
},
|
||||
"user_message_not_found": "Não foi possível encontrar a mensagem original do usuário"
|
||||
"user_message_not_found": "Não foi possível encontrar a mensagem original do usuário",
|
||||
"unknown": "Erro desconhecido",
|
||||
"pause_placeholder": "Interrompido"
|
||||
},
|
||||
"export": {
|
||||
"assistant": "Assistente",
|
||||
@@ -393,7 +494,11 @@
|
||||
"topN_tooltip": "Número de resultados correspondentes retornados, quanto maior o valor, mais resultados correspondentes, mas mais tokens são consumidos",
|
||||
"url_added": "URL adicionada",
|
||||
"url_placeholder": "Digite a URL, várias URLs separadas por enter",
|
||||
"urls": "URLs"
|
||||
"urls": "URLs",
|
||||
"dimensions": "Dimensão de incorporação",
|
||||
"dimensions_size_tooltip": "Tamanho da dimensão de incorporação, quanto maior o valor, maior a dimensão de incorporação, mas também maior o consumo de tokens",
|
||||
"dimensions_size_placeholder": "Valor padrão (não recomendado alterar)",
|
||||
"dimensions_size_too_large": "A dimensão de incorporação não pode exceder o limite do contexto do modelo ({{max_context}})"
|
||||
},
|
||||
"languages": {
|
||||
"arabic": "Árabe",
|
||||
@@ -505,20 +610,63 @@
|
||||
"completed": "Completo",
|
||||
"invoking": "Em execução",
|
||||
"raw": "Bruto",
|
||||
"preview": "Pré-visualização"
|
||||
"preview": "Pré-visualização",
|
||||
"error": "Ocorreu um erro"
|
||||
},
|
||||
"topic.added": "Tópico adicionado com sucesso",
|
||||
"upgrade.success.button": "Reiniciar",
|
||||
"upgrade.success.content": "Reinicie para concluir a atualização",
|
||||
"upgrade.success.title": "Atualização bem-sucedida",
|
||||
"warn.notion.exporting": "Exportando para Notion, não solicite novamente a exportação!",
|
||||
"warning.rate.limit": "Envio muito frequente, aguarde {{seconds}} segundos antes de tentar novamente"
|
||||
"warning.rate.limit": "Envio muito frequente, aguarde {{seconds}} segundos antes de tentar novamente",
|
||||
"agents": {
|
||||
"imported": "Importado com sucesso",
|
||||
"import.error": "Falha na importação"
|
||||
},
|
||||
"citation": "{{count}} conteúdo(s) citado(s)",
|
||||
"error.invalid.nutstore": "Configuração inválida do Nutstore",
|
||||
"error.invalid.nutstore_token": "Token do Nutstore inválido",
|
||||
"processing": "Processando...",
|
||||
"error.siyuan.export": "Falha ao exportar nota do Siyuan, verifique o estado da conexão e confira a configuração no documento",
|
||||
"error.siyuan.no_config": "Endereço da API ou token do Siyuan não configurado",
|
||||
"success.siyuan.export": "Exportado para o Siyuan com sucesso",
|
||||
"warn.yuque.exporting": "Exportando para Yuque, por favor não solicite a exportação novamente!",
|
||||
"warn.siyuan.exporting": "Exportando para o Siyuan, por favor não solicite a exportação novamente!",
|
||||
"download.success": "Download bem-sucedido",
|
||||
"download.failed": "Falha no download"
|
||||
},
|
||||
"minapp": {
|
||||
"sidebar.add.title": "Adicionar à barra lateral",
|
||||
"sidebar.remove.title": "Remover da barra lateral",
|
||||
"sidebar.hide.title": "Ocultar aplicativo",
|
||||
"title": "Pequeno aplicativo"
|
||||
"title": "Pequeno aplicativo",
|
||||
"popup": {
|
||||
"refresh": "Atualizar",
|
||||
"close": "Fechar aplicativo",
|
||||
"minimize": "Minimizar aplicativo",
|
||||
"devtools": "Ferramentas de Desenvolvedor",
|
||||
"openExternal": "Abrir no navegador",
|
||||
"rightclick_copyurl": "Copiar URL com botão direito",
|
||||
"open_link_external_on": "Atual: Abrir links no navegador",
|
||||
"open_link_external_off": "Atual: Abrir links em janela padrão"
|
||||
},
|
||||
"sidebar": {
|
||||
"add": {
|
||||
"title": "Adicionar à barra lateral"
|
||||
},
|
||||
"remove": {
|
||||
"title": "Remover da barra lateral"
|
||||
},
|
||||
"remove_custom": {
|
||||
"title": "Excluir aplicativo personalizado"
|
||||
},
|
||||
"hide": {
|
||||
"title": "Ocultar"
|
||||
},
|
||||
"close": {
|
||||
"title": "Fechar"
|
||||
},
|
||||
"closeall": {
|
||||
"title": "Fechar Tudo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"miniwindow": {
|
||||
"clipboard": {
|
||||
@@ -534,13 +682,17 @@
|
||||
"copy_last_message": "Pressione C para copiar",
|
||||
"esc": "Pressione ESC {{action}}",
|
||||
"esc_back": "Voltar",
|
||||
"esc_close": "Fechar janela"
|
||||
"esc_close": "Fechar janela",
|
||||
"backspace_clear": "Pressione Backspace para limpar"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": {
|
||||
"empty": "Pergunte a {{model}} para obter ajuda...",
|
||||
"title": "O que você quer fazer com o texto abaixo"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"pin": "Fixar na frente"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
@@ -577,7 +729,9 @@
|
||||
"free": "Grátis",
|
||||
"rerank": "Reclassificar",
|
||||
"websearch": "Procurar na web"
|
||||
}
|
||||
},
|
||||
"rerank_model_not_support_provider": "Atualmente o modelo de reclassificação não suporta este provedor ({{provider}})",
|
||||
"enable_tool_use": "Chamada de ferramentas"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "Expandir caixa de diálogo",
|
||||
@@ -609,7 +763,59 @@
|
||||
"regenerate.confirm": "Isso substituirá as imagens já geradas, deseja continuar?",
|
||||
"seed": "Semente Aleatória",
|
||||
"seed_tip": "A mesma semente e palavra-chave podem gerar imagens semelhantes",
|
||||
"title": "Imagem"
|
||||
"title": "Imagem",
|
||||
"mode": {
|
||||
"generate": "Gerar imagem",
|
||||
"edit": "Editar",
|
||||
"remix": "Misturar",
|
||||
"upscale": "Aumentar"
|
||||
},
|
||||
"generate": {
|
||||
"model_tip": "Versão do modelo: V2 é o modelo mais recente da interface, V2A é o modelo rápido, V_1 é o modelo de primeira geração e _TURBO é a versão acelerada",
|
||||
"number_images_tip": "Número de imagens geradas por vez",
|
||||
"seed_tip": "Controla a aleatoriedade na geração das imagens, usado para reproduzir resultados idênticos",
|
||||
"negative_prompt_tip": "Descreve elementos que você não deseja ver nas imagens; suportado apenas nas versões V_1, V_1_TURBO, V_2 e V_2_TURBO",
|
||||
"magic_prompt_option_tip": "Otimização inteligente do prompt para melhorar os resultados da geração",
|
||||
"style_type_tip": "Estilo de geração da imagem, aplicável apenas às versões V_2 e superiores"
|
||||
},
|
||||
"edit": {
|
||||
"image_file": "Imagem editada",
|
||||
"model_tip": "Edição localizada apenas suporta as versões V_2 e V_2_TURBO",
|
||||
"number_images_tip": "Número de resultados da edição gerados",
|
||||
"style_type_tip": "Estilo da imagem editada, disponível apenas para a versão V_2 ou superior",
|
||||
"seed_tip": "Controla a aleatoriedade do resultado da edição",
|
||||
"magic_prompt_option_tip": "Otimização inteligente da palavra-chave de edição"
|
||||
},
|
||||
"remix": {
|
||||
"model_tip": "Selecione a versão do modelo de IA para reutilização",
|
||||
"image_file": "Imagem de referência",
|
||||
"image_weight": "Peso da imagem de referência",
|
||||
"image_weight_tip": "Ajuste o impacto da imagem de referência",
|
||||
"number_images_tip": "Número de resultados de remix gerados",
|
||||
"seed_tip": "Controla a aleatoriedade dos resultados do remix",
|
||||
"style_type_tip": "Estilo da imagem após o remix, aplicável apenas às versões V_2 ou superiores",
|
||||
"negative_prompt_tip": "Descreva elementos que não devem aparecer nos resultados do remix",
|
||||
"magic_prompt_option_tip": "Otimização inteligente das palavras-chave do remix"
|
||||
},
|
||||
"upscale": {
|
||||
"image_file": "Imagem que precisa ser ampliada",
|
||||
"resemblance": "Similaridade",
|
||||
"resemblance_tip": "Controla o nível de semelhança entre o resultado ampliado e a imagem original",
|
||||
"detail": "Detalhe",
|
||||
"detail_tip": "Controla o grau de realce dos detalhes na imagem ampliada",
|
||||
"number_images_tip": "Número de resultados de ampliação gerados",
|
||||
"seed_tip": "Controla a aleatoriedade do resultado de ampliação",
|
||||
"magic_prompt_option_tip": "Otimização inteligente da dica de ampliação"
|
||||
},
|
||||
"magic_prompt_option": "Aprimoramento de Prompt",
|
||||
"model": "Versão",
|
||||
"aspect_ratio": "Proporção da Imagem",
|
||||
"style_type": "Estilo",
|
||||
"learn_more": "Saiba Mais",
|
||||
"prompt_placeholder_edit": "Digite sua descrição da imagem, use aspas \"duplas\" para desenho textual",
|
||||
"proxy_required": "Atualmente é necessário ativar um proxy para visualizar as imagens geradas, no futuro será suportada a conexão direta dentro do país",
|
||||
"image_file_required": "Por favor, faça o upload da imagem primeiro",
|
||||
"image_file_retry": "Por favor, faça o upload novamente da imagem"
|
||||
},
|
||||
"plantuml": {
|
||||
"download": {
|
||||
@@ -672,7 +878,9 @@
|
||||
"xirang": "XiRang do Nuvem Telecom",
|
||||
"yi": "ZeroUmTudo",
|
||||
"zhinao": "360 Inteligência Artificial",
|
||||
"zhipu": "ZhiPu IA"
|
||||
"zhipu": "ZhiPu IA",
|
||||
"voyageai": "Voyage AI",
|
||||
"qiniu": "Qiniu Cloud"
|
||||
},
|
||||
"restore": {
|
||||
"confirm": "Tem certeza de que deseja restaurar os dados?",
|
||||
@@ -714,7 +922,6 @@
|
||||
"advanced.title": "Configurações avançadas",
|
||||
"assistant": "Assistente padrão",
|
||||
"assistant.model_params": "Parâmetros do modelo",
|
||||
"assistant.show.icon": "Mostrar ícone do modelo",
|
||||
"assistant.title": "Assistente padrão",
|
||||
"data": {
|
||||
"app_data": "Dados do aplicativo",
|
||||
@@ -780,13 +987,13 @@
|
||||
"notion.split_size_placeholder": "Insira o limite de blocos por página (padrão 90)",
|
||||
"notion.title": "Configurações do Notion",
|
||||
"obsidian": {
|
||||
"folder": "Pasta",
|
||||
"folder_placeholder": "Insira o nome da pasta",
|
||||
"tags": "Tags Globais",
|
||||
"tags_placeholder": "Insira o nome da tag, use vírgulas para separar múltiplas tags, o Obsidian não permite números puros",
|
||||
"title": "Configuração do Obsidian",
|
||||
"vault": "Cofre",
|
||||
"vault_placeholder": "Insira o nome do cofre"
|
||||
"default_vault": "Repositório Obsidian padrão",
|
||||
"default_vault_placeholder": "Selecione o repositório Obsidian padrão",
|
||||
"default_vault_loading": "Obtendo repositório Obsidian...",
|
||||
"default_vault_no_vaults": "Nenhum repositório Obsidian encontrado",
|
||||
"default_vault_fetch_error": "Falha ao obter o repositório Obsidian",
|
||||
"default_vault_export_failed": "Falha na exportação"
|
||||
},
|
||||
"title": "Configurações de dados",
|
||||
"webdav": {
|
||||
@@ -818,7 +1025,26 @@
|
||||
"title": "WebDAV",
|
||||
"user": "Nome de usuário WebDAV",
|
||||
"maxBackups": "Número máximo de backups",
|
||||
"maxBackups.unlimited": "Sem limite"
|
||||
"maxBackups.unlimited": "Sem limite",
|
||||
"backup.manager.title": "Gerenciamento de Dados de Backup",
|
||||
"backup.manager.refresh": "Atualizar",
|
||||
"backup.manager.delete.selected": "Excluir Selecionado",
|
||||
"backup.manager.delete.text": "Excluir",
|
||||
"backup.manager.restore.text": "Restaurar",
|
||||
"backup.manager.restore.success": "Restauração bem-sucedida, o aplicativo será atualizado em alguns segundos",
|
||||
"backup.manager.restore.error": "Falha na restauração",
|
||||
"backup.manager.delete.confirm.title": "Confirmar Exclusão",
|
||||
"backup.manager.delete.confirm.single": "Tem certeza de que deseja excluir o arquivo de backup \"{{fileName}}\"? Esta ação não pode ser desfeita.",
|
||||
"backup.manager.delete.confirm.multiple": "Tem certeza de que deseja excluir os {{count}} arquivos de backup selecionados? Esta ação não pode ser desfeita.",
|
||||
"backup.manager.delete.success.single": "Exclusão bem-sucedida",
|
||||
"backup.manager.delete.success.multiple": "{{count}} arquivos de backup excluídos com sucesso",
|
||||
"backup.manager.delete.error": "Falha ao excluir",
|
||||
"backup.manager.fetch.error": "Falha ao obter arquivos de backup",
|
||||
"backup.manager.select.files.delete": "Selecione os arquivos de backup que deseja excluir",
|
||||
"backup.manager.columns.fileName": "Nome do Arquivo",
|
||||
"backup.manager.columns.modifiedTime": "Data de Modificação",
|
||||
"backup.manager.columns.size": "Tamanho",
|
||||
"backup.manager.columns.actions": "Ações"
|
||||
},
|
||||
"yuque": {
|
||||
"check": {
|
||||
@@ -834,16 +1060,73 @@
|
||||
"title": "Configuração do Yuque",
|
||||
"token": "Token do Yuque",
|
||||
"token_placeholder": "Insira o Token do Yuque"
|
||||
}
|
||||
},
|
||||
"export_menu": {
|
||||
"title": "Exportar Configurações do Menu",
|
||||
"image": "Exportar como Imagem",
|
||||
"markdown": "Exportar como Markdown",
|
||||
"markdown_reason": "Exportar como Markdown (incluindo pensamentos)",
|
||||
"notion": "Exportar para Notion",
|
||||
"yuque": "Exportar para Yuque",
|
||||
"obsidian": "Exportar para Obsidian",
|
||||
"siyuan": "Exportar para Siyuan Notes",
|
||||
"joplin": "Exportar para Joplin",
|
||||
"docx": "Exportar como Word"
|
||||
},
|
||||
"siyuan": {
|
||||
"check": {
|
||||
"title": "Detecção de Conexão",
|
||||
"button": "Detectar",
|
||||
"empty_config": "Por favor, preencha o endereço da API e o token",
|
||||
"success": "Conexão bem-sucedida",
|
||||
"fail": "Falha na conexão, verifique o endereço da API e o token",
|
||||
"error": "Erro na conexão, verifique a conexão de rede"
|
||||
},
|
||||
"title": "Configuração do Siyuan Notebook",
|
||||
"api_url": "Endereço da API",
|
||||
"api_url_placeholder": "Exemplo: http://127.0.0.1:6806",
|
||||
"token": "Token da API",
|
||||
"token.help": "Obtenha em Siyuan Notebook -> Configurações -> Sobre",
|
||||
"token_placeholder": "Por favor, insira o token do Siyuan Notebook",
|
||||
"box_id": "ID do Caderno",
|
||||
"box_id_placeholder": "Por favor, insira o ID do caderno",
|
||||
"root_path": "Caminho Raiz do Documento",
|
||||
"root_path_placeholder": "Exemplo: /CherryStudio"
|
||||
},
|
||||
"nutstore": {
|
||||
"title": "Configuração do Nutstore",
|
||||
"isLogin": "Logado",
|
||||
"notLogin": "Não Logado",
|
||||
"login.button": "Entrar",
|
||||
"logout.button": "Sair",
|
||||
"logout.title": "Tem certeza de que deseja sair da conta do Nutstore?",
|
||||
"logout.content": "Após sair, não será possível fazer backup ou restaurar dados do Nutstore",
|
||||
"checkConnection.name": "Verificar Conexão",
|
||||
"checkConnection.success": "Conectado ao Nutstore",
|
||||
"checkConnection.fail": "Falha na conexão com o Nutstore",
|
||||
"username": "Nome de usuário do Nutstore",
|
||||
"path": "Caminho de armazenamento do Nutstore",
|
||||
"path.placeholder": "Por favor, insira o caminho de armazenamento do Nutstore",
|
||||
"backup.button": "Fazer backup para o Nutstore",
|
||||
"restore.button": "Restaurar do Nutstore",
|
||||
"pathSelector.title": "Caminho de armazenamento do Nutstore",
|
||||
"pathSelector.return": "Voltar",
|
||||
"pathSelector.currentPath": "Caminho atual",
|
||||
"new_folder.button.confirm": "Confirmar",
|
||||
"new_folder.button.cancel": "Cancelar",
|
||||
"new_folder.button": "Nova Pasta"
|
||||
},
|
||||
"divider.basic": "Configurações Básicas",
|
||||
"divider.cloud_storage": "Configurações de Armazenamento em Nuvem",
|
||||
"divider.export_settings": "Configurações de Exportação",
|
||||
"divider.third_party": "Conexões de Terceiros",
|
||||
"message_title.use_topic_naming.title": "Usar modelo de nomeação por tópico para criar títulos das mensagens exportadas",
|
||||
"message_title.use_topic_naming.help": "Ativando esta opção, será usado um modelo de nomeação por tópico para criar os títulos das mensagens exportadas. Esta configuração também afetará todas as formas de exportação feitas por meio de Markdown."
|
||||
},
|
||||
"display.assistant.title": "Configurações do assistente",
|
||||
"display.custom.css": "CSS personalizado",
|
||||
"display.custom.css.cherrycss": "Obter do cherrycss.com",
|
||||
"display.custom.css.placeholder": "/* Escreva seu CSS personalizado aqui */",
|
||||
"display.minApp.disabled": "Aplicativos ocultos",
|
||||
"display.minApp.empty": "Arraste os aplicativos que deseja ocultar da esquerda para cá",
|
||||
"display.minApp.title": "Configurações de exibição de aplicativos",
|
||||
"display.minApp.visible": "Aplicativos visíveis",
|
||||
"display.sidebar.chat.hiddenMessage": "O assistente é uma funcionalidade básica e não pode ser ocultada",
|
||||
"display.sidebar.disabled": "Ícones ocultos",
|
||||
"display.sidebar.empty": "Arraste as funcionalidades que deseja ocultar da esquerda para cá",
|
||||
@@ -864,7 +1147,6 @@
|
||||
"general.display.title": "Configurações de exibição",
|
||||
"general.emoji_picker": "Seletor de emojis",
|
||||
"general.image_upload": "Carregar imagem",
|
||||
"general.manually_check_update.title": "Desativar verificação de atualização",
|
||||
"general.reset.button": "Redefinir",
|
||||
"general.reset.title": "Redefinir dados",
|
||||
"general.restore.button": "Restaurar",
|
||||
@@ -930,11 +1212,94 @@
|
||||
"serverPlural": "Servidores",
|
||||
"serverSingular": "Servidor",
|
||||
"title": "Servidores MCP",
|
||||
"toggleError": "Falha ao alternar",
|
||||
"type": "Tipo",
|
||||
"updateError": "Falha ao atualizar servidor",
|
||||
"updateSuccess": "Servidor atualizado com sucesso",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"errors": {
|
||||
"32000": "Falha ao iniciar o servidor MCP, verifique se todos os parâmetros foram preenchidos corretamente conforme o tutorial"
|
||||
},
|
||||
"tabs": {
|
||||
"general": "Geral",
|
||||
"description": "Descrição",
|
||||
"tools": "Ferramentas",
|
||||
"prompts": "Prompts",
|
||||
"resources": "Recursos"
|
||||
},
|
||||
"tools": {
|
||||
"inputSchema": "Esquema de Entrada",
|
||||
"availableTools": "Ferramentas Disponíveis",
|
||||
"noToolsAvailable": "Nenhuma Ferramenta Disponível",
|
||||
"loadError": "Falha ao Obter Ferramentas"
|
||||
},
|
||||
"prompts": {
|
||||
"availablePrompts": "Dicas disponíveis",
|
||||
"noPromptsAvailable": "Nenhuma dica disponível",
|
||||
"arguments": "Argumentos",
|
||||
"requiredField": "Campo obrigatório",
|
||||
"genericError": "Erro ao buscar dicas",
|
||||
"loadError": "Falha ao carregar dicas"
|
||||
},
|
||||
"resources": {
|
||||
"noResourcesAvailable": "Nenhum recurso disponível",
|
||||
"availableResources": "Recursos disponíveis",
|
||||
"uri": "URI",
|
||||
"mimeType": "Tipo MIME",
|
||||
"size": "Tamanho",
|
||||
"blob": "Dados binários",
|
||||
"blobInvisible": "Ocultar dados binários",
|
||||
"text": "Texto"
|
||||
},
|
||||
"types": {
|
||||
"inMemory": "Integrado",
|
||||
"sse": "SSE",
|
||||
"streamableHttp": "Streaming",
|
||||
"stdio": "STDIO"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizar Servidor",
|
||||
"selectProvider": "Selecione o provedor:",
|
||||
"discoverMcpServers": "Descobrir servidores MCP",
|
||||
"discoverMcpServersDescription": "Acesse a plataforma para descobrir servidores MCP disponíveis",
|
||||
"getToken": "Obter token de API",
|
||||
"getTokenDescription": "Obtenha um token de API pessoal da sua conta",
|
||||
"setToken": "Digite seu token",
|
||||
"tokenRequired": "Token de API é obrigatório",
|
||||
"tokenPlaceholder": "Digite o token de API aqui",
|
||||
"button": "Sincronizar",
|
||||
"error": "Erro ao sincronizar servidor MCP",
|
||||
"success": "Servidor MCP sincronizado com sucesso",
|
||||
"unauthorized": "Sincronização não autorizada",
|
||||
"noServersAvailable": "Nenhum servidor MCP disponível"
|
||||
},
|
||||
"sse": "Eventos do Servidor (sse)",
|
||||
"streamableHttp": "HTTP Transmitido em Fluxo (streamableHttp)",
|
||||
"stdio": "Entrada/Saída Padrão (stdio)",
|
||||
"inMemory": "Na Memória",
|
||||
"headers": "Cabeçalhos da Requisição",
|
||||
"headersTooltip": "Cabeçalhos HTTP personalizados para as requisições",
|
||||
"searchNpx": "Buscar MCP",
|
||||
"newServer": "Servidor MCP",
|
||||
"startError": "Falha ao Iniciar",
|
||||
"editMcpJson": "Editar Configuração MCP",
|
||||
"installHelp": "Obter Ajuda com a Instalação",
|
||||
"deleteServer": "Excluir Servidor",
|
||||
"deleteServerConfirm": "Tem certeza de que deseja excluir este servidor?",
|
||||
"registry": "Fonte de Gerenciamento de Pacotes",
|
||||
"registryTooltip": "Selecione uma fonte alternativa para instalar pacotes, caso tenha problemas de rede com a fonte padrão.",
|
||||
"registryDefault": "Padrão",
|
||||
"not_support": "Modelo Não Suportado",
|
||||
"user": "Usuário",
|
||||
"system": "Sistema",
|
||||
"timeout": "Tempo Limite",
|
||||
"timeoutTooltip": "Tempo limite (em segundos) para as requisições deste servidor; o padrão é 60 segundos",
|
||||
"provider": "Fornecedor",
|
||||
"providerUrl": "URL do Fornecedor",
|
||||
"logoUrl": "URL do Logotipo",
|
||||
"tags": "Etiquetas",
|
||||
"tagsPlaceholder": "Digite as etiquetas",
|
||||
"providerPlaceholder": "Nome do Fornecedor",
|
||||
"advancedSettings": "Configurações Avançadas"
|
||||
},
|
||||
"messages.divider": "Divisor de mensagens",
|
||||
"messages.grid_columns": "Número de colunas da grade de mensagens",
|
||||
@@ -1046,13 +1411,29 @@
|
||||
"docs_more_details": "Obter mais detalhes",
|
||||
"get_api_key": "Clique aqui para obter a chave",
|
||||
"is_not_support_array_content": "Ativar modo compatível",
|
||||
"no_models": "Por favor, adicione um modelo antes de verificar a conexão da API",
|
||||
"not_checked": "Não verificado",
|
||||
"remove_duplicate_keys": "Remover chaves duplicadas",
|
||||
"remove_invalid_keys": "Remover chaves inválidas",
|
||||
"search": "Procurar plataforma de modelos...",
|
||||
"search_placeholder": "Procurar ID ou nome do modelo",
|
||||
"title": "Serviços de Modelos"
|
||||
"title": "Serviços de Modelos",
|
||||
"oauth": {
|
||||
"button": "Entrar com a conta {{provider}}",
|
||||
"description": "Este serviço é fornecido por <website>{{provider}}</website>",
|
||||
"official_website": "Site Oficial"
|
||||
},
|
||||
"notes": {
|
||||
"title": "Observação do Modelo",
|
||||
"placeholder": "Por favor, insira o conteúdo no formato Markdown...",
|
||||
"markdown_editor_default_value": "Área de Visualização"
|
||||
},
|
||||
"basic_auth": "Autenticação HTTP",
|
||||
"basic_auth.tip": "Aplica-se a instâncias implantadas por meio de servidor (consulte a documentação). Atualmente, apenas o esquema Basic é suportado (RFC7617).",
|
||||
"basic_auth.user_name": "Nome de usuário",
|
||||
"basic_auth.user_name.tip": "Deixe em branco para desativar",
|
||||
"basic_auth.password": "Senha",
|
||||
"bills": "Contas",
|
||||
"no_models_for_check": "Não há modelos disponíveis para verificação (por exemplo, modelos de conversa)"
|
||||
},
|
||||
"proxy": {
|
||||
"mode": {
|
||||
@@ -1129,8 +1510,111 @@
|
||||
"description": "O Tavily é um mecanismo de busca projetado especificamente para agentes de IA, oferecendo resultados em tempo real, precisos, sugestões inteligentes de consulta e capacidades de pesquisa aprofundada",
|
||||
"title": "Tavily"
|
||||
},
|
||||
"title": "Pesquisa na Web"
|
||||
}
|
||||
"title": "Pesquisa na Web",
|
||||
"overwrite": "Substituir provedor de pesquisa",
|
||||
"overwrite_tooltip": "Forçar o uso do provedor de pesquisa em vez do modelo de linguagem grande para pesquisas",
|
||||
"subscribe": "Assinar lista negra",
|
||||
"subscribe_update": "Atualizar agora",
|
||||
"subscribe_add": "Adicionar assinatura",
|
||||
"subscribe_url": "Endereço da fonte de assinatura",
|
||||
"subscribe_name": "Nome alternativo",
|
||||
"subscribe_name.placeholder": "Nome alternativo usado quando a fonte assinada não tem nome",
|
||||
"subscribe_add_success": "Fonte de assinatura adicionada com sucesso!",
|
||||
"subscribe_delete": "Excluir fonte de assinatura",
|
||||
"apikey": "Chave API",
|
||||
"free": "Grátis",
|
||||
"content_limit": "Limite de comprimento do conteúdo",
|
||||
"content_limit_tooltip": "Limita o comprimento do conteúdo nos resultados da pesquisa; conteúdo excedente será truncado"
|
||||
},
|
||||
"miniapps": {
|
||||
"open_link_external": {
|
||||
"title": "Abrir link em nova janela do navegador"
|
||||
},
|
||||
"custom": {
|
||||
"title": "Aplicativo Personalizado",
|
||||
"edit_title": "Editar Aplicativo Personalizado",
|
||||
"save_success": "Aplicativo personalizado salvo com sucesso.",
|
||||
"save_error": "Falha ao salvar o aplicativo personalizado.",
|
||||
"remove_success": "Aplicativo personalizado excluído com sucesso.",
|
||||
"remove_error": "Falha ao excluir o aplicativo personalizado.",
|
||||
"logo_upload_success": "Logo enviada com sucesso.",
|
||||
"logo_upload_error": "Falha no envio da Logo.",
|
||||
"id": "ID",
|
||||
"id_error": "A ID é obrigatória.",
|
||||
"id_placeholder": "Digite a ID",
|
||||
"name": "Nome",
|
||||
"name_error": "O nome é obrigatório.",
|
||||
"name_placeholder": "Digite o nome",
|
||||
"url": "URL",
|
||||
"url_error": "A URL é obrigatória.",
|
||||
"url_placeholder": "Digite a URL",
|
||||
"logo": "Logo",
|
||||
"logo_url": "URL da Logo",
|
||||
"logo_file": "Enviar Arquivo da Logo",
|
||||
"logo_url_label": "URL da Logo",
|
||||
"logo_url_placeholder": "Digite a URL da Logo",
|
||||
"logo_upload_label": "Enviar Logo",
|
||||
"logo_upload_button": "Enviar",
|
||||
"save": "Salvar",
|
||||
"edit_description": "Edite aqui as configurações do aplicativo personalizado. Cada aplicativo deve conter os campos id, name, url e logo.",
|
||||
"placeholder": "Digite a configuração do aplicativo personalizado (formato JSON)",
|
||||
"duplicate_ids": "IDs duplicadas encontradas: {{ids}}",
|
||||
"conflicting_ids": "Conflito com IDs padrão: {{ids}}"
|
||||
},
|
||||
"title": "Configurações do Mini Aplicativo",
|
||||
"disabled": "Mini Aplicativos Ocultos",
|
||||
"empty": "Arraste para cá os mini aplicativos que deseja ocultar",
|
||||
"visible": "Mini Aplicativos Visíveis",
|
||||
"cache_settings": "Configurações de Cache",
|
||||
"cache_title": "Quantidade de Mini Aplicativos no Cache",
|
||||
"cache_description": "Defina o número máximo de mini aplicativos que permanecerão ativos simultaneamente",
|
||||
"reset_tooltip": "Redefinir para os valores padrão",
|
||||
"display_title": "Configurações de Exibição dos Mini Aplicativos",
|
||||
"sidebar_title": "Exibição de Mini Aplicativos Ativos na Barra Lateral",
|
||||
"sidebar_description": "Defina se os mini aplicativos ativos serão exibidos na barra lateral",
|
||||
"cache_change_notice": "As alterações entrarão em vigor após a abertura ou remoção dos mini aplicativos até atingir o número definido"
|
||||
},
|
||||
"quickPhrase": {
|
||||
"title": "Frases Rápidas",
|
||||
"add": "Adicionar Frase",
|
||||
"edit": "Editar Frase",
|
||||
"titleLabel": "Título",
|
||||
"contentLabel": "Conteúdo",
|
||||
"titlePlaceholder": "Por favor, insira o título da frase",
|
||||
"contentPlaceholder": "Por favor, insira o conteúdo da frase. É permitido usar variáveis, e em seguida pressionar a tecla Tab para localizar rapidamente as variáveis e editá-las. Por exemplo:\\nPlaneje uma rota de ${from} para ${to} e envie para ${email}.",
|
||||
"delete": "Excluir Frase",
|
||||
"deleteConfirm": "A frase excluída não poderá ser recuperada. Deseja continuar?",
|
||||
"locationLabel": "Adicionar Localização",
|
||||
"global": "Frase Global",
|
||||
"assistant": "Frase do Assistente"
|
||||
},
|
||||
"quickPanel": {
|
||||
"title": "Menu de Atalho",
|
||||
"close": "Fechar",
|
||||
"select": "Selecionar",
|
||||
"page": "Página",
|
||||
"confirm": "Confirmar",
|
||||
"back": "Voltar",
|
||||
"forward": "Avançar",
|
||||
"multiple": "Múltipla Seleção"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Configurações de Privacidade",
|
||||
"enable_privacy_mode": "Enviar relatórios de erro e estatísticas de forma anônima"
|
||||
},
|
||||
"assistant.icon.type": "Tipo de ícone do modelo",
|
||||
"assistant.icon.type.model": "Ícone do modelo",
|
||||
"assistant.icon.type.emoji": "Emoji",
|
||||
"assistant.icon.type.none": "Não mostrar",
|
||||
"general.auto_check_update.title": "Atualização automática",
|
||||
"input.show_translate_confirm": "Mostrar diálogo de confirmação de tradução",
|
||||
"messages.prompt": "Exibir palavra-chave",
|
||||
"messages.input.enable_quick_triggers": "Ativar menu rápido com '/' e '@'",
|
||||
"messages.input.enable_delete_model": "Ativar tecla de exclusão para remover modelos/anexos inseridos",
|
||||
"messages.math_engine.none": "Nenhum",
|
||||
"models.manage.add_listed": "Adicionar modelo da lista",
|
||||
"models.manage.remove_listed": "Remover modelo da lista",
|
||||
"zoom.title": "Zoom da página"
|
||||
},
|
||||
"translate": {
|
||||
"any.language": "qualquer idioma",
|
||||
@@ -1155,7 +1639,10 @@
|
||||
"scroll_sync.disable": "Desativar sincronização de rolagem",
|
||||
"scroll_sync.enable": "Ativar sincronização de rolagem",
|
||||
"title": "Tradução",
|
||||
"tooltip.newline": "Quebra de linha"
|
||||
"tooltip.newline": "Quebra de linha",
|
||||
"menu": {
|
||||
"description": "Traduzir o conteúdo da caixa de entrada atual"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"quit": "Sair",
|
||||
@@ -1169,4 +1656,4 @@
|
||||
"visualization": "Visualização"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
afterClose={onClose}
|
||||
okText={t('agents.add.title')}
|
||||
width={800}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Form
|
||||
ref={formRef}
|
||||
|
||||
@@ -43,14 +43,17 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
|
||||
)
|
||||
|
||||
const exportAgent = useCallback(async () => {
|
||||
const result = {
|
||||
name: agent.name,
|
||||
emoji: agent.emoji,
|
||||
group: agent.group,
|
||||
prompt: agent.prompt,
|
||||
description: agent.description,
|
||||
type: 'agent'
|
||||
}
|
||||
const result = [
|
||||
{
|
||||
name: agent.name,
|
||||
emoji: agent.emoji,
|
||||
group: agent.group,
|
||||
prompt: agent.prompt,
|
||||
description: agent.description,
|
||||
regularPhrases: agent.regularPhrases,
|
||||
type: 'agent'
|
||||
}
|
||||
]
|
||||
|
||||
const resultStr = JSON.stringify(result, null, 2)
|
||||
|
||||
@@ -216,7 +219,6 @@ const HeaderInfoEmoji = styled.div`
|
||||
border-radius: var(--list-item-border-radius);
|
||||
font-size: 26px;
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
flex-shrink: 0;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
@@ -245,12 +247,12 @@ const AgentCardContainer = styled.div`
|
||||
|
||||
--shadow-color: rgba(0, 0, 0, 0.05);
|
||||
box-shadow:
|
||||
0 5px 7px -3px var(--shadow-color),
|
||||
0 2px 3px -4px var(--shadow-color);
|
||||
0 5px 7px -3px var(--color-border-soft),
|
||||
0 2px 3px -4px var(--color-border-soft);
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 10px 15px -3px var(--shadow-color),
|
||||
0 4px 6px -4px var(--shadow-color);
|
||||
0 10px 15px -3px var(--color-border-soft),
|
||||
0 4px 6px -4px var(--color-border-soft);
|
||||
transform: translateY(-2px);
|
||||
|
||||
${AgentCardHeaderInfoAction} ${HeaderInfoEmoji} {
|
||||
|
||||
@@ -64,7 +64,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
type: 'agent',
|
||||
topics: [],
|
||||
messages: [],
|
||||
defaultModel: getDefaultModel()
|
||||
defaultModel: getDefaultModel(),
|
||||
regularPhrases: agent.regularPhrases || []
|
||||
}
|
||||
addAgent(newAgent)
|
||||
}
|
||||
@@ -93,7 +94,13 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title={t('agents.import.title')} open={open} onCancel={onCancel} footer={null} centered>
|
||||
<Modal
|
||||
title={t('agents.import.title')}
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
footer={null}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Form form={form} onFinish={onFinish} layout="vertical">
|
||||
<Form.Item>
|
||||
<Radio.Group value={importType} onChange={(e) => setImportType(e.target.value)}>
|
||||
|
||||
@@ -39,6 +39,7 @@ const PopupContainer: React.FC = () => {
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
footer={null}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Container>
|
||||
{agents.length > 0 && (
|
||||
|
||||
@@ -84,9 +84,7 @@ const App: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
}
|
||||
|
||||
const handleFileChange = async (info: any) => {
|
||||
console.log(info)
|
||||
const file = info.fileList[info.fileList.length - 1]?.originFileObj
|
||||
console.log(file)
|
||||
setFileList(info.fileList.slice(-1))
|
||||
|
||||
if (file) {
|
||||
@@ -179,7 +177,9 @@ const App: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
setIsModalVisible(false)
|
||||
setFileList([])
|
||||
}}
|
||||
footer={null}>
|
||||
footer={null}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Form form={form} onFinish={handleAddCustomApp} layout="vertical">
|
||||
<Form.Item
|
||||
name="id"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import ListItem from '@renderer/components/ListItem'
|
||||
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
|
||||
import Logger from '@renderer/config/logger'
|
||||
import db from '@renderer/databases'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
@@ -107,7 +108,7 @@ const FilesPage: FC = () => {
|
||||
// This case should ideally not happen if relatedBlocks were found,
|
||||
// but handle it just in case: only delete blocks.
|
||||
await db.message_blocks.bulkDelete(blockIdsToDelete)
|
||||
console.log(
|
||||
Logger.log(
|
||||
`Deleted ${blockIdsToDelete.length} blocks related to file ${fileId}. No associated messages found (unexpected).`
|
||||
)
|
||||
return
|
||||
@@ -154,9 +155,9 @@ const FilesPage: FC = () => {
|
||||
await db.message_blocks.bulkDelete(blockIdsToDelete)
|
||||
})
|
||||
|
||||
console.log(`Deleted ${blockIdsToDelete.length} blocks and updated relevant topic messages for file ${fileId}.`)
|
||||
Logger.log(`Deleted ${blockIdsToDelete.length} blocks and updated relevant topic messages for file ${fileId}.`)
|
||||
} catch (error) {
|
||||
console.error(`Error updating topics or deleting blocks for file ${fileId}:`, error)
|
||||
Logger.error(`Error updating topics or deleting blocks for file ${fileId}:`, error)
|
||||
window.modal.error({ content: t('files.delete.db_error'), centered: true }) // 提示数据库操作失败
|
||||
// Consider whether to attempt to restore the physical file (usually difficult)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HolderOutlined } from '@ant-design/icons'
|
||||
import { QuickPanelListItem, QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import Logger from '@renderer/config/logger'
|
||||
import {
|
||||
isGenerateImageModel,
|
||||
isSupportedReasoningEffortModel,
|
||||
@@ -36,7 +37,6 @@ import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||
import dayjs from 'dayjs'
|
||||
import Logger from 'electron-log/renderer'
|
||||
import { debounce, isEmpty } from 'lodash'
|
||||
import {
|
||||
AtSign,
|
||||
@@ -184,7 +184,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[DEBUG] Starting to send message')
|
||||
Logger.log('[DEBUG] Starting to send message')
|
||||
|
||||
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE)
|
||||
|
||||
@@ -193,7 +193,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const uploadedFiles = await FileManager.uploadFiles(files)
|
||||
|
||||
const baseUserMessage: MessageInputBaseParams = { assistant, topic, content: text }
|
||||
console.log('baseUserMessage', baseUserMessage)
|
||||
Logger.log('baseUserMessage', baseUserMessage)
|
||||
|
||||
// getUserMessage()
|
||||
if (uploadedFiles) {
|
||||
@@ -220,10 +220,10 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const { message, blocks } = getUserMessage(baseUserMessage)
|
||||
|
||||
currentMessageId.current = message.id
|
||||
console.log('[DEBUG] Created message and blocks:', message, blocks)
|
||||
console.log('[DEBUG] Dispatching _sendMessage')
|
||||
Logger.log('[DEBUG] Created message and blocks:', message, blocks)
|
||||
Logger.log('[DEBUG] Dispatching _sendMessage')
|
||||
dispatch(_sendMessage(message, blocks, assistant, topic.id))
|
||||
console.log('[DEBUG] _sendMessage dispatched')
|
||||
Logger.log('[DEBUG] _sendMessage dispatched')
|
||||
|
||||
// Clear input
|
||||
setText('')
|
||||
@@ -459,7 +459,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
}, 200)
|
||||
|
||||
if (spaceClickCount === 2) {
|
||||
console.log('Triple space detected - trigger translation')
|
||||
Logger.log('Triple space detected - trigger translation')
|
||||
setSpaceClickCount(0)
|
||||
setIsTranslating(true)
|
||||
translate()
|
||||
@@ -741,7 +741,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus()
|
||||
}, [assistant])
|
||||
}, [assistant, topic])
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => resizeTextArea(), 0)
|
||||
@@ -757,9 +757,14 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('focus', () => {
|
||||
const onFocus = () => {
|
||||
if (document.activeElement?.closest('.ant-modal')) {
|
||||
return
|
||||
}
|
||||
textareaRef.current?.focus()
|
||||
})
|
||||
}
|
||||
window.addEventListener('focus', onFocus)
|
||||
return () => window.removeEventListener('focus', onFocus)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -898,10 +903,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
styles={{ textarea: TextareaStyle }}
|
||||
onFocus={(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
setInputFocus(true)
|
||||
const textArea = e.target
|
||||
if (textArea) {
|
||||
const length = textArea.value.length
|
||||
textArea.setSelectionRange(length, length)
|
||||
if (e.target.value.length === 0) {
|
||||
e.target.setSelectionRange(0, 0)
|
||||
}
|
||||
}}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
@@ -970,6 +973,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
setInputValue={setText}
|
||||
resizeTextArea={resizeTextArea}
|
||||
ToolbarButton={ToolbarButton}
|
||||
assistantObj={assistant}
|
||||
/>
|
||||
<Tooltip placement="top" title={t('chat.input.clear', { Command: cleanTopicShortcut })} arrow>
|
||||
<ToolbarButton type="text" onClick={clearTopic}>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import { QuickPanelListItem, QuickPanelOpenOptions } from '@renderer/components/QuickPanel/types'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import QuickPhraseService from '@renderer/services/QuickPhraseService'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { QuickPhrase } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Plus, Zap } from 'lucide-react'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Input, Modal, Radio, Space, Tooltip } from 'antd'
|
||||
import { BotMessageSquare, Plus, Zap } from 'lucide-react'
|
||||
import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export interface QuickPhrasesButtonRef {
|
||||
openQuickPanel: () => void
|
||||
@@ -17,22 +20,37 @@ interface Props {
|
||||
setInputValue: React.Dispatch<React.SetStateAction<string>>
|
||||
resizeTextArea: () => void
|
||||
ToolbarButton: any
|
||||
assistantObj: Assistant
|
||||
}
|
||||
|
||||
const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton }: Props) => {
|
||||
const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton, assistantObj }: Props) => {
|
||||
const [quickPhrasesList, setQuickPhrasesList] = useState<QuickPhrase[]>([])
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [formData, setFormData] = useState({ title: '', content: '', location: 'global' })
|
||||
const { t } = useTranslation()
|
||||
const quickPanel = useQuickPanel()
|
||||
const activeAssistantId = useAppSelector(
|
||||
(state) =>
|
||||
state.assistants.assistants.find((a) => a.id === assistantObj.id)?.id || state.assistants.defaultAssistant.id
|
||||
)
|
||||
const { assistant, updateAssistant } = useAssistant(activeAssistantId)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const loadQuickListPhrases = useCallback(
|
||||
async (regularPhrases: QuickPhrase[] = []) => {
|
||||
const phrases = await QuickPhraseService.getAll()
|
||||
if (regularPhrases.length) {
|
||||
setQuickPhrasesList([...regularPhrases, ...phrases])
|
||||
return
|
||||
}
|
||||
const assistantPrompts = assistant.regularPhrases || []
|
||||
setQuickPhrasesList([...assistantPrompts, ...phrases])
|
||||
},
|
||||
[assistant]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const loadQuickListPhrases = async () => {
|
||||
const phrases = await QuickPhraseService.getAll()
|
||||
setQuickPhrasesList(phrases.reverse())
|
||||
}
|
||||
loadQuickListPhrases()
|
||||
}, [])
|
||||
}, [loadQuickListPhrases])
|
||||
|
||||
const handlePhraseSelect = useCallback(
|
||||
(phrase: QuickPhrase) => {
|
||||
@@ -56,20 +74,52 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton
|
||||
[setInputValue, resizeTextArea]
|
||||
)
|
||||
|
||||
const handleModalOk = async () => {
|
||||
if (!formData.title.trim() || !formData.content.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedPrompts = [
|
||||
...(assistant.regularPhrases || []),
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
title: formData.title,
|
||||
content: formData.content,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
]
|
||||
if (formData.location === 'assistant') {
|
||||
// 添加到助手的 regularPhrases
|
||||
await updateAssistant({ ...assistant, regularPhrases: updatedPrompts })
|
||||
} else {
|
||||
// 添加到全局 Quick Phrases
|
||||
await QuickPhraseService.add(formData)
|
||||
}
|
||||
setIsModalOpen(false)
|
||||
setFormData({ title: '', content: '', location: 'global' })
|
||||
if (formData.location === 'assistant') {
|
||||
await loadQuickListPhrases(updatedPrompts)
|
||||
return
|
||||
}
|
||||
await loadQuickListPhrases()
|
||||
}
|
||||
|
||||
const phraseItems = useMemo(() => {
|
||||
const newList: QuickPanelListItem[] = quickPhrasesList.map((phrase) => ({
|
||||
const newList: QuickPanelListItem[] = quickPhrasesList.map((phrase, index) => ({
|
||||
label: phrase.title,
|
||||
description: phrase.content,
|
||||
icon: <Zap />,
|
||||
icon: index < (assistant.regularPhrases?.length || 0) ? <BotMessageSquare /> : <Zap />,
|
||||
action: () => handlePhraseSelect(phrase)
|
||||
}))
|
||||
|
||||
newList.push({
|
||||
label: t('settings.quickPhrase.add') + '...',
|
||||
icon: <Plus />,
|
||||
action: () => navigate('/settings/quickPhrase')
|
||||
action: () => setIsModalOpen(true)
|
||||
})
|
||||
return newList
|
||||
}, [quickPhrasesList, t, handlePhraseSelect, navigate])
|
||||
}, [quickPhrasesList, t, handlePhraseSelect, assistant])
|
||||
|
||||
const quickPanelOpenOptions = useMemo<QuickPanelOpenOptions>(
|
||||
() => ({
|
||||
@@ -97,12 +147,71 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, ToolbarButton
|
||||
}))
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('settings.quickPhrase.title')} arrow>
|
||||
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||
<Zap size={18} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<>
|
||||
<Tooltip placement="top" title={t('settings.quickPhrase.title')} arrow>
|
||||
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||
<Zap size={18} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
|
||||
<Modal
|
||||
title={t('settings.quickPhrase.add')}
|
||||
open={isModalOpen}
|
||||
onOk={handleModalOk}
|
||||
onCancel={() => {
|
||||
setIsModalOpen(false)
|
||||
setFormData({ title: '', content: '', location: 'global' })
|
||||
}}
|
||||
width={520}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<div>
|
||||
<Label>{t('settings.quickPhrase.titleLabel')}</Label>
|
||||
<Input
|
||||
placeholder={t('settings.quickPhrase.titlePlaceholder')}
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>{t('settings.quickPhrase.contentLabel')}</Label>
|
||||
<Input.TextArea
|
||||
placeholder={t('settings.quickPhrase.contentPlaceholder')}
|
||||
value={formData.content}
|
||||
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
|
||||
rows={6}
|
||||
style={{ resize: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>{t('settings.quickPhrase.locationLabel', '添加位置')}</Label>
|
||||
<Radio.Group
|
||||
value={formData.location}
|
||||
onChange={(e) => setFormData({ ...formData, location: e.target.value })}>
|
||||
<Radio value="global">
|
||||
<Zap size={20} style={{ paddingRight: '4px', verticalAlign: 'middle', paddingBottom: '3px' }} />
|
||||
{t('settings.quickPhrase.global', '全局快速短语')}
|
||||
</Radio>
|
||||
<Radio value="assistant">
|
||||
<BotMessageSquare
|
||||
size={20}
|
||||
style={{ paddingRight: '4px', verticalAlign: 'middle', paddingBottom: '3px' }}
|
||||
/>
|
||||
{t('settings.quickPhrase.assistant', '助手提示词')}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Label = styled.div`
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
export default memo(QuickPhrasesButton)
|
||||
|
||||
@@ -35,7 +35,7 @@ const MODEL_SUPPORTED_OPTIONS: Record<string, ThinkingOption[]> = {
|
||||
default: ['off', 'low', 'medium', 'high'],
|
||||
grok: ['off', 'low', 'high'],
|
||||
gemini: ['off', 'low', 'medium', 'high', 'auto'],
|
||||
qwen: ['off', 'low', 'medium', 'high', 'auto']
|
||||
qwen: ['off', 'low', 'medium', 'high']
|
||||
}
|
||||
|
||||
// 选项转换映射表:当选项不支持时使用的替代选项
|
||||
|
||||
@@ -147,9 +147,9 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||
</StickyWrapper>
|
||||
<CodeContent
|
||||
ref={codeContentRef}
|
||||
isShowLineNumbers={codeShowLineNumbers}
|
||||
isUnwrapped={isUnwrapped}
|
||||
isCodeWrappable={codeWrappable}
|
||||
$isShowLineNumbers={codeShowLineNumbers}
|
||||
$isUnwrapped={isUnwrapped}
|
||||
$isCodeWrappable={codeWrappable}
|
||||
// dangerouslySetInnerHTML={{ __html: html }}
|
||||
style={{
|
||||
padding: '1px',
|
||||
@@ -272,7 +272,7 @@ const CodeBlockWrapper = styled.div`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const CodeContent = styled.div<{ isShowLineNumbers: boolean; isUnwrapped: boolean; isCodeWrappable: boolean }>`
|
||||
const CodeContent = styled.div<{ $isShowLineNumbers: boolean; $isUnwrapped: boolean; $isCodeWrappable: boolean }>`
|
||||
transition: opacity 0.3s ease;
|
||||
.shiki {
|
||||
padding: 1em;
|
||||
@@ -285,13 +285,13 @@ const CodeContent = styled.div<{ isShowLineNumbers: boolean; isUnwrapped: boolea
|
||||
.line {
|
||||
display: block;
|
||||
min-height: 1.3rem;
|
||||
padding-left: ${(props) => (props.isShowLineNumbers ? '2rem' : '0')};
|
||||
padding-left: ${(props) => (props.$isShowLineNumbers ? '2rem' : '0')};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isShowLineNumbers &&
|
||||
props.$isShowLineNumbers &&
|
||||
`
|
||||
code {
|
||||
counter-reset: step;
|
||||
@@ -311,8 +311,8 @@ const CodeContent = styled.div<{ isShowLineNumbers: boolean; isUnwrapped: boolea
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.isCodeWrappable &&
|
||||
!props.isUnwrapped &&
|
||||
props.$isCodeWrappable &&
|
||||
!props.$isUnwrapped &&
|
||||
`
|
||||
code .line * {
|
||||
word-wrap: break-word;
|
||||
|
||||
@@ -43,6 +43,7 @@ const Mermaid: React.FC<Props> = ({ chart }) => {
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(renderMermaidBase, 0)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -199,6 +199,7 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
width={1000}
|
||||
transitionName="animation-move-down"
|
||||
centered
|
||||
footer={[
|
||||
<Space key="download-buttons">
|
||||
|
||||
@@ -224,6 +224,7 @@ const PlantUMLPopupCantaier: React.FC<PlantUMLPopupProps> = ({ resolve, diagram
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
width={1000}
|
||||
transitionName="animation-move-down"
|
||||
centered
|
||||
footer={[
|
||||
<Space key="download-buttons">
|
||||
|
||||
@@ -12,14 +12,14 @@ import CitationsList from '../CitationsList'
|
||||
|
||||
function CitationBlock({ block }: { block: CitationMessageBlock }) {
|
||||
const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, block.id))
|
||||
const hasGeminiBlock = block.response?.source === WebSearchSource.GEMINI
|
||||
const hasCitations = useMemo(() => {
|
||||
const hasGeminiBlock = block.response?.source === WebSearchSource.GEMINI
|
||||
return (
|
||||
(formattedCitations && formattedCitations.length > 0) ||
|
||||
hasGeminiBlock ||
|
||||
(block.knowledge && block.knowledge.length > 0)
|
||||
)
|
||||
}, [formattedCitations, block.response, block.knowledge])
|
||||
}, [formattedCitations, block.knowledge, hasGeminiBlock])
|
||||
|
||||
if (block.status === MessageBlockStatus.PROCESSING) {
|
||||
return <Spinner text="message.searching" />
|
||||
@@ -29,12 +29,10 @@ function CitationBlock({ block }: { block: CitationMessageBlock }) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isGemini = block.response?.source === WebSearchSource.GEMINI
|
||||
|
||||
return (
|
||||
<>
|
||||
{block.status === MessageBlockStatus.SUCCESS &&
|
||||
(isGemini ? (
|
||||
(hasGeminiBlock ? (
|
||||
<>
|
||||
<CitationsList citations={formattedCitations} />
|
||||
<SearchEntryPoint
|
||||
|
||||
@@ -16,7 +16,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock }> = ({ block }) =>
|
||||
|
||||
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
|
||||
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
|
||||
return <Alert description={t(`error.http.${block.error.status}`)} type="error" />
|
||||
return <Alert description={t(`error.http.${block.error.status}`)} message={block.error?.message} type="error" />
|
||||
}
|
||||
if (block?.error?.message) {
|
||||
const errorKey = `error.${block.error.message}`
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { GroundingSupport } from '@google/genai'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import type { RootState } from '@renderer/store'
|
||||
import { selectFormattedCitationsByBlockId } from '@renderer/store/messageBlock'
|
||||
import type { Model } from '@renderer/types'
|
||||
import { type Model, WebSearchSource } from '@renderer/types'
|
||||
import type { MainTextMessageBlock, Message } from '@renderer/types/newMessage'
|
||||
import { Flex } from 'antd'
|
||||
import React, { useMemo } from 'react'
|
||||
@@ -47,25 +48,96 @@ const MainTextBlock: React.FC<Props> = ({ block, citationBlockId, role, mentions
|
||||
return content
|
||||
}
|
||||
|
||||
// FIXME:性能问题,需要优化
|
||||
// Replace all citation numbers in the content with formatted citations
|
||||
formattedCitations.forEach((citation) => {
|
||||
const citationNum = citation.number
|
||||
const supData = {
|
||||
id: citationNum,
|
||||
url: citation.url,
|
||||
title: citation.title || citation.hostname || '',
|
||||
content: citation.content?.substring(0, 200)
|
||||
}
|
||||
const isLink = citation.url.startsWith('http')
|
||||
const citationJson = encodeHTML(JSON.stringify(supData))
|
||||
const supTag = `<sup data-citation='${citationJson}'>${citationNum}</sup>`
|
||||
const citationTag = isLink ? `[${supTag}](${citation.url})` : supTag
|
||||
switch (block.citationReferences[0].citationBlockSource) {
|
||||
case WebSearchSource.OPENAI_COMPATIBLE:
|
||||
case WebSearchSource.OPENAI: {
|
||||
formattedCitations.forEach((citation) => {
|
||||
const citationNum = citation.number
|
||||
const supData = {
|
||||
id: citationNum,
|
||||
url: citation.url,
|
||||
title: citation.title || citation.hostname || '',
|
||||
content: citation.content?.substring(0, 200)
|
||||
}
|
||||
const citationJson = encodeHTML(JSON.stringify(supData))
|
||||
|
||||
// Replace all occurrences of [citationNum] with the formatted citation
|
||||
const regex = new RegExp(`\\[${citationNum}\\]`, 'g')
|
||||
content = content.replace(regex, citationTag)
|
||||
})
|
||||
// Handle[<sup>N</sup>](url)
|
||||
const preFormattedRegex = new RegExp(`\\[<sup>${citationNum}</sup>\\]\\(.*?\\)`, 'g')
|
||||
|
||||
const citationTag = `[<sup data-citation='${citationJson}'>${citationNum}</sup>](${citation.url})`
|
||||
|
||||
content = content.replace(preFormattedRegex, citationTag)
|
||||
})
|
||||
break
|
||||
}
|
||||
case WebSearchSource.GEMINI: {
|
||||
// First pass: Add basic citation marks using metadata
|
||||
let processedContent = content
|
||||
const firstCitation = formattedCitations[0]
|
||||
if (firstCitation?.metadata) {
|
||||
firstCitation.metadata.forEach((support: GroundingSupport) => {
|
||||
const citationNums = support.groundingChunkIndices!
|
||||
|
||||
if (support.segment) {
|
||||
const text = support.segment.text!
|
||||
// 生成引用标记
|
||||
const basicTag = citationNums
|
||||
.map((citationNum) => {
|
||||
const citation = formattedCitations.find((c) => c.number === citationNum + 1)
|
||||
return citation ? `[<sup>${citationNum + 1}</sup>](${citation.url})` : ''
|
||||
})
|
||||
.join('')
|
||||
|
||||
// 在文本后面添加引用标记,而不是替换
|
||||
if (text && basicTag) {
|
||||
processedContent = processedContent.replace(text, `${text}${basicTag}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
content = processedContent
|
||||
}
|
||||
// Second pass: Replace basic citations with full citation data
|
||||
formattedCitations.forEach((citation) => {
|
||||
const citationNum = citation.number
|
||||
const supData = {
|
||||
id: citationNum,
|
||||
url: citation.url,
|
||||
title: citation.title || citation.hostname || '',
|
||||
content: citation.content?.substring(0, 200)
|
||||
}
|
||||
const citationJson = encodeHTML(JSON.stringify(supData))
|
||||
|
||||
// Replace basic citation with full citation including data
|
||||
const basicCitationRegex = new RegExp(`\\[<sup>${citationNum}</sup>\\]\\(${citation.url}\\)`, 'g')
|
||||
const fullCitationTag = `[<sup data-citation='${citationJson}'>${citationNum}</sup>](${citation.url})`
|
||||
content = content.replace(basicCitationRegex, fullCitationTag)
|
||||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
// FIXME:性能问题,需要优化
|
||||
// Replace all citation numbers and pre-formatted links with formatted citations
|
||||
formattedCitations.forEach((citation) => {
|
||||
const citationNum = citation.number
|
||||
const supData = {
|
||||
id: citationNum,
|
||||
url: citation.url,
|
||||
title: citation.title || citation.hostname || '',
|
||||
content: citation.content?.substring(0, 200)
|
||||
}
|
||||
const isLink = citation.url.startsWith('http')
|
||||
const citationJson = encodeHTML(JSON.stringify(supData))
|
||||
|
||||
// Handle both plain references [N] and pre-formatted links [<sup>N</sup>](url)
|
||||
const plainRefRegex = new RegExp(`\\[${citationNum}\\]`, 'g')
|
||||
|
||||
const supTag = `<sup data-citation='${citationJson}'>${citationNum}</sup>`
|
||||
const citationTag = isLink ? `[${supTag}](${citation.url})` : supTag
|
||||
|
||||
content = content.replace(plainRefRegex, citationTag)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return content
|
||||
}, [block.content, block.citationReferences, citationBlockId, formattedCitations])
|
||||
|
||||
@@ -2,12 +2,35 @@ import { CheckOutlined } from '@ant-design/icons'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||
import { Collapse, message as antdMessage, Tooltip } from 'antd'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Lightbulb } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BarLoader from 'react-spinners/BarLoader'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Markdown from '../../Markdown/Markdown'
|
||||
|
||||
// Define variants outside the component if they don't depend on component's props/state directly
|
||||
// or inside if they do (though for this case, outside is fine).
|
||||
const lightbulbVariants = {
|
||||
thinking: {
|
||||
opacity: [1, 0.2, 1],
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.5, 1],
|
||||
repeat: Infinity
|
||||
}
|
||||
},
|
||||
idle: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.3, // Smooth transition to idle state
|
||||
ease: 'easeInOut'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
block: ThinkingMessageBlock
|
||||
}
|
||||
@@ -17,6 +40,8 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
||||
const { t } = useTranslation()
|
||||
const { messageFont, fontSize, thoughtAutoCollapse } = useSettings()
|
||||
const [activeKey, setActiveKey] = useState<'thought' | ''>(thoughtAutoCollapse ? '' : 'thought')
|
||||
const [thinkingTime, setThinkingTime] = useState(block.thinking_millsec || 0)
|
||||
const intervalId = useRef<NodeJS.Timeout>(null)
|
||||
|
||||
const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status])
|
||||
|
||||
@@ -50,30 +75,56 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
||||
}
|
||||
}, [block.content, t])
|
||||
|
||||
// FIXME: 这里统计的和请求处统计的有一定误差
|
||||
useEffect(() => {
|
||||
if (isThinking) {
|
||||
intervalId.current = setInterval(() => {
|
||||
setThinkingTime((prev) => prev + 100)
|
||||
}, 100)
|
||||
} else if (intervalId.current) {
|
||||
// 立即清除计时器
|
||||
clearInterval(intervalId.current)
|
||||
intervalId.current = null
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalId.current) {
|
||||
window.clearInterval(intervalId.current)
|
||||
}
|
||||
}
|
||||
}, [isThinking])
|
||||
|
||||
const thinkingTimeSeconds = useMemo(() => (thinkingTime / 1000).toFixed(1), [thinkingTime])
|
||||
|
||||
if (!block.content) {
|
||||
return null
|
||||
}
|
||||
|
||||
const thinkingTime = block.thinking_millsec || 0
|
||||
const thinkingTimeSeconds = (thinkingTime / 1000).toFixed(1)
|
||||
|
||||
return (
|
||||
<CollapseContainer
|
||||
activeKey={activeKey}
|
||||
size="small"
|
||||
onChange={() => setActiveKey((key) => (key ? '' : 'thought'))}
|
||||
className="message-thought-container"
|
||||
expandIconPosition="end"
|
||||
items={[
|
||||
{
|
||||
key: 'thought',
|
||||
label: (
|
||||
<MessageTitleLabel>
|
||||
<motion.span
|
||||
style={{ height: '18px' }}
|
||||
variants={lightbulbVariants}
|
||||
animate={isThinking ? 'thinking' : 'idle'}
|
||||
initial="idle">
|
||||
<Lightbulb size={18} />
|
||||
</motion.span>
|
||||
<ThinkingText>
|
||||
{t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', {
|
||||
seconds: thinkingTimeSeconds
|
||||
})}
|
||||
</ThinkingText>
|
||||
{isThinking && <BarLoader color="#9254de" />}
|
||||
{/* {isThinking && <BarLoader color="#9254de" />} */}
|
||||
{!isThinking && (
|
||||
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||
<ActionButton
|
||||
@@ -104,6 +155,7 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
|
||||
|
||||
const CollapseContainer = styled(Collapse)`
|
||||
margin-bottom: 15px;
|
||||
max-width: 960px;
|
||||
`
|
||||
|
||||
const MessageTitleLabel = styled.div`
|
||||
@@ -111,7 +163,7 @@ const MessageTitleLabel = styled.div`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
gap: 15px;
|
||||
gap: 4px;
|
||||
`
|
||||
|
||||
const ThinkingText = styled.span`
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import type { RootState } from '@renderer/store'
|
||||
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||
import type {
|
||||
ErrorMessageBlock,
|
||||
FileMessageBlock,
|
||||
ImageMessageBlock,
|
||||
MainTextMessageBlock,
|
||||
Message,
|
||||
MessageBlock,
|
||||
PlaceholderMessageBlock,
|
||||
ThinkingMessageBlock,
|
||||
TranslationMessageBlock
|
||||
} from '@renderer/types/newMessage'
|
||||
import type { ImageMessageBlock, MainTextMessageBlock, Message, MessageBlock } from '@renderer/types/newMessage'
|
||||
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { AnimatePresence, motion } from 'motion/react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
@@ -26,8 +17,41 @@ import ThinkingBlock from './ThinkingBlock'
|
||||
import ToolBlock from './ToolBlock'
|
||||
import TranslationBlock from './TranslationBlock'
|
||||
|
||||
interface AnimatedBlockWrapperProps {
|
||||
children: React.ReactNode
|
||||
enableAnimation: boolean
|
||||
}
|
||||
|
||||
const blockWrapperVariants = {
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: { duration: 0.3, type: 'spring', bounce: 0 }
|
||||
},
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
x: 10
|
||||
},
|
||||
static: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: { duration: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
const AnimatedBlockWrapper: React.FC<AnimatedBlockWrapperProps> = ({ children, enableAnimation }) => {
|
||||
return (
|
||||
<motion.div
|
||||
variants={blockWrapperVariants}
|
||||
initial={enableAnimation ? 'hidden' : 'static'}
|
||||
animate={enableAnimation ? 'visible' : 'static'}>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
blocks: MessageBlock[] | string[] // 可以接收块ID数组或MessageBlock数组
|
||||
blocks: string[] // 可以接收块ID数组或MessageBlock数组
|
||||
messageStatus?: Message['status']
|
||||
message: Message
|
||||
}
|
||||
@@ -54,26 +78,30 @@ const MessageBlockRenderer: React.FC<Props> = ({ blocks, message }) => {
|
||||
// 根据blocks类型处理渲染数据
|
||||
const renderedBlocks = blocks.map((blockId) => blockEntities[blockId]).filter(Boolean)
|
||||
const groupedBlocks = useMemo(() => filterImageBlockGroups(renderedBlocks), [renderedBlocks])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence mode="sync">
|
||||
{groupedBlocks.map((block) => {
|
||||
if (Array.isArray(block)) {
|
||||
const groupKey = block.map((imageBlock) => imageBlock.id).join('-')
|
||||
return (
|
||||
<ImageBlockGroup key={block.map((imageBlock) => imageBlock.id).join('-')}>
|
||||
{block.map((imageBlock) => (
|
||||
<ImageBlock key={imageBlock.id} block={imageBlock as ImageMessageBlock} />
|
||||
))}
|
||||
</ImageBlockGroup>
|
||||
<AnimatedBlockWrapper key={groupKey} enableAnimation={message.status.includes('ing')}>
|
||||
<ImageBlockGroup>
|
||||
{block.map((imageBlock) => (
|
||||
<ImageBlock key={imageBlock.id} block={imageBlock as ImageMessageBlock} />
|
||||
))}
|
||||
</ImageBlockGroup>
|
||||
</AnimatedBlockWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
let blockComponent: React.ReactNode = null
|
||||
|
||||
switch (block.type) {
|
||||
case MessageBlockType.UNKNOWN:
|
||||
if (block.status === MessageBlockStatus.PROCESSING) {
|
||||
return <PlaceholderBlock key={block.id} block={block as PlaceholderMessageBlock} />
|
||||
blockComponent = <PlaceholderBlock key={block.id} block={block} />
|
||||
}
|
||||
return null
|
||||
break
|
||||
case MessageBlockType.MAIN_TEXT:
|
||||
case MessageBlockType.CODE: {
|
||||
const mainTextBlock = block as MainTextMessageBlock
|
||||
@@ -82,7 +110,7 @@ const MessageBlockRenderer: React.FC<Props> = ({ blocks, message }) => {
|
||||
// No longer need to retrieve the full citation block here
|
||||
// const citationBlock = citationBlockId ? (blockEntities[citationBlockId] as CitationMessageBlock) : undefined
|
||||
|
||||
return (
|
||||
blockComponent = (
|
||||
<MainTextBlock
|
||||
key={block.id}
|
||||
block={mainTextBlock}
|
||||
@@ -91,30 +119,43 @@ const MessageBlockRenderer: React.FC<Props> = ({ blocks, message }) => {
|
||||
role={message.role}
|
||||
/>
|
||||
)
|
||||
break
|
||||
}
|
||||
case MessageBlockType.IMAGE:
|
||||
return <ImageBlock key={block.id} block={block as ImageMessageBlock} />
|
||||
blockComponent = <ImageBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.FILE:
|
||||
return <FileBlock key={block.id} block={block as FileMessageBlock} />
|
||||
blockComponent = <FileBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.TOOL:
|
||||
return <ToolBlock key={block.id} block={block} />
|
||||
blockComponent = <ToolBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.CITATION:
|
||||
return <CitationBlock key={block.id} block={block} />
|
||||
blockComponent = <CitationBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.ERROR:
|
||||
return <ErrorBlock key={block.id} block={block as ErrorMessageBlock} />
|
||||
blockComponent = <ErrorBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.THINKING:
|
||||
return <ThinkingBlock key={block.id} block={block as ThinkingMessageBlock} />
|
||||
// case MessageBlockType.CODE:
|
||||
// return <CodeBlock key={block.id} block={block as CodeMessageBlock} />
|
||||
blockComponent = <ThinkingBlock key={block.id} block={block} />
|
||||
break
|
||||
case MessageBlockType.TRANSLATION:
|
||||
return <TranslationBlock key={block.id} block={block as TranslationMessageBlock} />
|
||||
blockComponent = <TranslationBlock key={block.id} block={block} />
|
||||
break
|
||||
default:
|
||||
// Cast block to any for console.warn to fix linter error
|
||||
console.warn('Unsupported block type in MessageBlockRenderer:', (block as any).type, block)
|
||||
return null
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatedBlockWrapper
|
||||
key={block.type === MessageBlockType.UNKNOWN ? 'placeholder' : block.id}
|
||||
enableAnimation={message.status.includes('ing')}>
|
||||
{blockComponent}
|
||||
</AnimatedBlockWrapper>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Citation {
|
||||
content?: string
|
||||
showFavicon?: boolean
|
||||
type?: string
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
interface CitationsListProps {
|
||||
@@ -207,9 +208,7 @@ const WebSearchCard = styled.div`
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--list-item-border-radius);
|
||||
background-color: var(--color-background);
|
||||
transition: all 0.3s ease;
|
||||
`
|
||||
|
||||
@@ -10,35 +10,6 @@ interface Props {
|
||||
}
|
||||
|
||||
const MessageContent: React.FC<Props> = ({ message }) => {
|
||||
// const { t } = useTranslation()
|
||||
// if (message.status === 'pending') {
|
||||
// return (
|
||||
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (message.status === 'searching') {
|
||||
// return (
|
||||
// <SearchingContainer>
|
||||
// <Search size={24} />
|
||||
// <SearchingText>{t('message.searching')}</SearchingText>
|
||||
// <BarLoader color="#1677ff" />
|
||||
// </SearchingContainer>
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (message.status === 'error') {
|
||||
// return <MessageError message={message} />
|
||||
// }
|
||||
|
||||
// if (message.type === '@' && model) {
|
||||
// const content = `[@${model.name}](#) ${getBriefInfo(message.content)}`
|
||||
// return <Markdown message={{ ...message, content }} />
|
||||
// }
|
||||
// const toolUseRegex = /<tool_use>([\s\S]*?)<\/tool_use>/g
|
||||
|
||||
// console.log('message', message)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { ErrorMessageBlock } from '@renderer/types/newMessage'
|
||||
import { Alert as AntdAlert } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const MessageError: FC<{ block: ErrorMessageBlock }> = ({ block }) => {
|
||||
return (
|
||||
<>
|
||||
{/* <Markdown block={block} role={role} />
|
||||
{block.error && (
|
||||
<Markdown
|
||||
message={{
|
||||
...block,
|
||||
content: formatErrorMessage(block.error)
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
<MessageErrorInfo block={block} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const MessageErrorInfo: FC<{ block: ErrorMessageBlock }> = ({ block }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
|
||||
console.log('block', block)
|
||||
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
|
||||
return <Alert description={t(`error.http.${block.error.status}`)} type="error" />
|
||||
}
|
||||
if (block?.error?.message) {
|
||||
return <Alert description={block.error.message} type="error" />
|
||||
}
|
||||
|
||||
return <Alert description={t('error.chat.response')} type="error" />
|
||||
}
|
||||
|
||||
const Alert = styled(AntdAlert)`
|
||||
margin: 15px 0 8px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default MessageError
|
||||
@@ -102,7 +102,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message }) => {
|
||||
<UserName isBubbleStyle={isBubbleStyle} theme={theme}>
|
||||
{username}
|
||||
</UserName>
|
||||
<MessageTime>{dayjs(message.createdAt).format('MM/DD HH:mm')}</MessageTime>
|
||||
<MessageTime>{dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')}</MessageTime>
|
||||
</UserWrap>
|
||||
</AvatarWrapper>
|
||||
</Container>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Image as AntdImage, Space } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
block: ImageMessageBlock
|
||||
}
|
||||
@@ -87,40 +88,42 @@ const MessageImage: FC<Props> = ({ block }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const renderToolbar =
|
||||
(currentImage: string, currentIndex: number) =>
|
||||
(
|
||||
_: any,
|
||||
{
|
||||
transform: { scale },
|
||||
actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn, onReset }
|
||||
}: any
|
||||
) => (
|
||||
<ToobarWrapper size={12} className="toolbar-wrapper">
|
||||
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||
<SwapOutlined onClick={onFlipX} />
|
||||
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||
<RotateRightOutlined onClick={onRotateRight} />
|
||||
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||
<UndoOutlined onClick={onReset} />
|
||||
<CopyOutlined onClick={() => onCopy(block.metadata?.generateImageResponse?.type!, currentImage)} />
|
||||
<DownloadOutlined onClick={() => onDownload(currentImage, currentIndex)} />
|
||||
</ToobarWrapper>
|
||||
)
|
||||
|
||||
const images = block.metadata?.generateImageResponse?.images?.length
|
||||
? block.metadata?.generateImageResponse?.images
|
||||
: // TODO 加file是否合适?
|
||||
block?.file?.path
|
||||
: block?.file?.path
|
||||
? [`file://${block?.file?.path}`]
|
||||
: []
|
||||
|
||||
return (
|
||||
<Container style={{ marginBottom: 8 }}>
|
||||
{images.map((image, index) => (
|
||||
<Image
|
||||
src={image}
|
||||
key={`image-${index}`}
|
||||
height={300}
|
||||
preview={{
|
||||
toolbarRender: (
|
||||
_,
|
||||
{
|
||||
transform: { scale },
|
||||
actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn, onReset }
|
||||
}
|
||||
) => (
|
||||
<ToobarWrapper size={12} className="toolbar-wrapper">
|
||||
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||
<SwapOutlined onClick={onFlipX} />
|
||||
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||
<RotateRightOutlined onClick={onRotateRight} />
|
||||
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||
<UndoOutlined onClick={onReset} />
|
||||
<CopyOutlined onClick={() => onCopy(block.metadata?.generateImageResponse?.type!, image)} />
|
||||
<DownloadOutlined onClick={() => onDownload(image, index)} />
|
||||
</ToobarWrapper>
|
||||
)
|
||||
}}
|
||||
style={{ maxWidth: 500, maxHeight: 500 }}
|
||||
preview={{ toolbarRender: renderToolbar(image, index) }}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { getMessageTitle } from '@renderer/services/MessagesService'
|
||||
import { translateText } from '@renderer/services/TranslateService'
|
||||
import { RootState } from '@renderer/store'
|
||||
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||
import type { Model } from '@renderer/types'
|
||||
import type { Assistant, Topic } from '@renderer/types'
|
||||
import type { Message } from '@renderer/types/newMessage'
|
||||
@@ -22,10 +23,16 @@ import {
|
||||
} from '@renderer/utils/export'
|
||||
// import { withMessageThought } from '@renderer/utils/formats'
|
||||
import { removeTrailingDoubleSpaces } from '@renderer/utils/markdown'
|
||||
import { findImageBlocks, findMainTextBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
import {
|
||||
findImageBlocks,
|
||||
findMainTextBlocks,
|
||||
findTranslationBlocks,
|
||||
getMainTextContent
|
||||
} from '@renderer/utils/messageUtils/find'
|
||||
import { Button, Dropdown, Popconfirm, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { AtSign, Copy, Languages, Menu, RefreshCw, Save, Share, Split, ThumbsUp, Trash } from 'lucide-react'
|
||||
import { FilePenLine } from 'lucide-react'
|
||||
import { FC, memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
@@ -61,7 +68,8 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
resendUserMessageWithEdit,
|
||||
getTranslationUpdater,
|
||||
appendAssistantResponse,
|
||||
editMessageBlocks
|
||||
editMessageBlocks,
|
||||
removeMessageBlock
|
||||
} = useMessageOperations(topic)
|
||||
const loading = useTopicLoading(topic)
|
||||
|
||||
@@ -87,7 +95,6 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
const onCopy = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
console.log('mainTextContent', mainTextContent)
|
||||
navigator.clipboard.writeText(removeTrailingDoubleSpaces(mainTextContent.trimStart()))
|
||||
|
||||
window.message.success({ content: t('message.copied'), key: 'copy-message' })
|
||||
@@ -164,7 +171,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
if (resendMessage) {
|
||||
resendUserMessageWithEdit(message, editedText, assistant)
|
||||
} else {
|
||||
editMessageBlocks([{ ...findMainTextBlocks(message)[0], content: editedText }])
|
||||
editMessageBlocks(message.id, { id: findMainTextBlocks(message)[0].id, content: editedText })
|
||||
}
|
||||
// // 更新消息内容,保留图片信息
|
||||
// await editMessage(message.id, {
|
||||
@@ -221,6 +228,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
[isTranslating, message, getTranslationUpdater, mainTextContent]
|
||||
)
|
||||
|
||||
const isEditable = useMemo(() => {
|
||||
return findMainTextBlocks(message).length === 1
|
||||
}, [message])
|
||||
|
||||
const dropdownItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -232,12 +243,16 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
window.api.file.save(fileName, mainTextContent)
|
||||
}
|
||||
},
|
||||
// {
|
||||
// label: t('common.edit'),
|
||||
// key: 'edit',
|
||||
// icon: <FilePenLine size={16} />,
|
||||
// onClick: onEdit
|
||||
// },
|
||||
...(isEditable
|
||||
? [
|
||||
{
|
||||
label: t('common.edit'),
|
||||
key: 'edit',
|
||||
icon: <FilePenLine size={16} />,
|
||||
onClick: onEdit
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: t('chat.message.new.branch'),
|
||||
key: 'new-branch',
|
||||
@@ -338,7 +353,7 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
].filter(Boolean)
|
||||
}
|
||||
],
|
||||
[message, messageContainerRef, mainTextContent, onNewBranch, t, topic.name, exportMenuOptions]
|
||||
[message, messageContainerRef, isEditable, onEdit, mainTextContent, onNewBranch, t, topic.name, exportMenuOptions]
|
||||
)
|
||||
|
||||
const onRegenerate = async (e: React.MouseEvent | undefined) => {
|
||||
@@ -369,6 +384,12 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
[message, editMessage]
|
||||
)
|
||||
|
||||
const blockEntities = useSelector(messageBlocksSelectors.selectEntities)
|
||||
const hasTranslationBlocks = useMemo(() => {
|
||||
const translationBlocks = findTranslationBlocks(message)
|
||||
return translationBlocks.length > 0
|
||||
}, [message])
|
||||
|
||||
return (
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||
{message.role === 'user' && (
|
||||
@@ -424,13 +445,52 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
label: item.emoji + ' ' + item.label,
|
||||
key: item.value,
|
||||
onClick: () => handleTranslate(item.value)
|
||||
}))
|
||||
// {
|
||||
// TODO 删除翻译块可以放在翻译块内
|
||||
// label: '✖ ' + t('translate.close'),
|
||||
// key: 'translate-close',
|
||||
// onClick: () => editMessage(message.id, { translatedContent: undefined })
|
||||
// }
|
||||
})),
|
||||
...(hasTranslationBlocks
|
||||
? [
|
||||
{ type: 'divider' as const },
|
||||
{
|
||||
label: '📋 ' + t('common.copy'),
|
||||
key: 'translate-copy',
|
||||
onClick: () => {
|
||||
const translationBlocks = message.blocks
|
||||
.map((blockId) => blockEntities[blockId])
|
||||
.filter((block) => block?.type === 'translation')
|
||||
|
||||
if (translationBlocks.length > 0) {
|
||||
const translationContent = translationBlocks
|
||||
.map((block) => block?.content || '')
|
||||
.join('\n\n')
|
||||
.trim()
|
||||
|
||||
if (translationContent) {
|
||||
navigator.clipboard.writeText(translationContent)
|
||||
window.message.success({ content: t('translate.copied'), key: 'translate-copy' })
|
||||
} else {
|
||||
window.message.warning({ content: t('translate.empty'), key: 'translate-copy' })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '✖ ' + t('translate.close'),
|
||||
key: 'translate-close',
|
||||
onClick: () => {
|
||||
const translationBlocks = message.blocks
|
||||
.map((blockId) => blockEntities[blockId])
|
||||
.filter((block) => block?.type === 'translation')
|
||||
.map((block) => block?.id)
|
||||
|
||||
if (translationBlocks.length > 0) {
|
||||
translationBlocks.forEach((blockId) => {
|
||||
if (blockId) removeMessageBlock(message.id, blockId)
|
||||
})
|
||||
window.message.success({ content: t('translate.closed'), key: 'translate-close' })
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
],
|
||||
onClick: (e) => e.domEvent.stopPropagation()
|
||||
}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user