From 7b9448f72eb821793b3f181725b98be00551b647 Mon Sep 17 00:00:00 2001 From: 1600822305 <1600822305@qq.com> Date: Sat, 12 Apr 2025 18:53:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=20TTS=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=9C=8D=E5=8A=A1=E5=B9=B6=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- asr-server/embedded.js | 123 ++ asr-server/index.html | 425 +++++ asr-server/package-lock.json | 854 +++++++++ asr-server/package.json | 10 + asr-server/server.js | 269 +++ asr-server/standalone.js | 114 ++ asr-server/start-server.bat | 5 + electron-builder.yml | 16 +- index.html | 425 +++++ public/asr-server/embedded.js | 123 ++ public/asr-server/index.html | 281 ++- public/asr-server/package-lock.json | 854 +++++++++ public/asr-server/package.json | 10 + public/asr-server/server.js | 141 +- public/asr-server/standalone.js | 114 ++ public/asr-server/start-server.bat | 5 + resources/asr-server/embedded.js | 123 ++ resources/asr-server/index.html | 425 +++++ resources/asr-server/package-lock.json | 854 +++++++++ resources/asr-server/package.json | 10 + resources/asr-server/server.js | 269 +++ resources/asr-server/standalone.js | 114 ++ resources/asr-server/start-server.bat | 5 + src/main/services/ASRServerService.ts | 43 +- src/main/utils/index.ts | 3 +- src/renderer/src/assets/asr-server/server.js | 2 +- .../src/components/TTSProgressBar.tsx | 64 +- src/renderer/src/config/prompts.ts | 61 + src/renderer/src/i18n/locales/en-us.json | 16 +- src/renderer/src/i18n/locales/ja-jp.json | 208 ++- src/renderer/src/i18n/locales/ru-ru.json | 30 +- src/renderer/src/i18n/locales/zh-cn.json | 12 + src/renderer/src/i18n/locales/zh-cn.json.bak | 1576 +++++++++++++++++ src/renderer/src/i18n/locales/zh-tw.json | 30 +- .../src/pages/home/Inputbar/Inputbar.tsx | 20 +- .../settings/TTSSettings/ASRSettings.tsx | 31 +- .../settings/TTSSettings/TTSSettings.tsx | 44 +- .../TTSSettings/VoiceCallSettings.tsx | 44 +- src/renderer/src/services/ASRServerService.ts | 69 +- src/renderer/src/services/ASRService.ts | 19 +- src/renderer/src/services/VoiceCallService.ts | 50 +- src/renderer/src/services/tts/MsTTSService.ts | 12 +- src/renderer/src/services/tts/TTSService.ts | 29 +- .../src/services/tts/TTSServiceFactory.ts | 13 +- .../src/services/tts/TTSTextFilter.ts | 20 + src/renderer/src/store/settings.ts | 22 + yarn.lock | 36 +- 47 files changed, 7752 insertions(+), 271 deletions(-) create mode 100644 asr-server/embedded.js create mode 100644 asr-server/index.html create mode 100644 asr-server/package-lock.json create mode 100644 asr-server/package.json create mode 100644 asr-server/server.js create mode 100644 asr-server/standalone.js create mode 100644 asr-server/start-server.bat create mode 100644 index.html create mode 100644 public/asr-server/embedded.js create mode 100644 public/asr-server/package-lock.json create mode 100644 public/asr-server/package.json create mode 100644 public/asr-server/standalone.js create mode 100644 public/asr-server/start-server.bat create mode 100644 resources/asr-server/embedded.js create mode 100644 resources/asr-server/index.html create mode 100644 resources/asr-server/package-lock.json create mode 100644 resources/asr-server/package.json create mode 100644 resources/asr-server/server.js create mode 100644 resources/asr-server/standalone.js create mode 100644 resources/asr-server/start-server.bat create mode 100644 src/renderer/src/i18n/locales/zh-cn.json.bak diff --git a/asr-server/embedded.js b/asr-server/embedded.js new file mode 100644 index 0000000000..68ae30e239 --- /dev/null +++ b/asr-server/embedded.js @@ -0,0 +1,123 @@ +/** + * 内置的ASR服务器模块 + * 这个文件可以直接在Electron中运行,不需要外部依赖 + */ + +// 使用Electron内置的Node.js模块 +const http = require('http') +const path = require('path') +const fs = require('fs') + +// 输出环境信息 +console.log('ASR Server (Embedded) starting...') +console.log('Node.js version:', process.version) +console.log('Current directory:', __dirname) +console.log('Current working directory:', process.cwd()) +console.log('Command line arguments:', process.argv) + +// 创建HTTP服务器 +const server = http.createServer((req, res) => { + try { + if (req.url === '/' || req.url === '/index.html') { + // 尝试多个可能的路径 + const possiblePaths = [ + // 当前目录 + path.join(__dirname, 'index.html'), + // 上级目录 + path.join(__dirname, '..', 'index.html'), + // 应用根目录 + path.join(process.cwd(), 'index.html') + ] + + console.log('Possible index.html paths:', possiblePaths) + + // 查找第一个存在的文件 + let indexPath = null + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + indexPath = p + console.log(`Found index.html at: ${p}`) + break + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + if (indexPath) { + // 读取文件内容并发送 + fs.readFile(indexPath, (err, data) => { + if (err) { + console.error('Error reading index.html:', err) + res.writeHead(500) + res.end('Error reading index.html') + return + } + + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(data) + }) + } else { + // 如果找不到文件,返回一个简单的HTML页面 + console.error('Could not find index.html, serving fallback page') + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(` + + + + ASR Server + + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:34515

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } else { + // 处理其他请求 + res.writeHead(404) + res.end('Not found') + } + } catch (error) { + console.error('Error handling request:', error) + res.writeHead(500) + res.end('Server error') + } +}) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + const port = 34515 + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/asr-server/index.html b/asr-server/index.html new file mode 100644 index 0000000000..cfa0db82fb --- /dev/null +++ b/asr-server/index.html @@ -0,0 +1,425 @@ + + + + + + + Cherry Studio ASR + + + + +

浏览器语音识别中继页面

+

这个页面需要在浏览器中保持打开,以便应用使用其语音识别功能。

+
正在连接到服务器...
+
+ + + + + \ No newline at end of file diff --git a/asr-server/package-lock.json b/asr-server/package-lock.json new file mode 100644 index 0000000000..8d3eb4035d --- /dev/null +++ b/asr-server/package-lock.json @@ -0,0 +1,854 @@ +{ + "name": "cherry-asr-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cherry-asr-server", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.13.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/asr-server/package.json b/asr-server/package.json new file mode 100644 index 0000000000..b09528cca8 --- /dev/null +++ b/asr-server/package.json @@ -0,0 +1,10 @@ +{ + "name": "cherry-asr-server", + "version": "1.0.0", + "description": "Cherry Studio ASR Server", + "main": "server.js", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.13.0" + } +} \ No newline at end of file diff --git a/asr-server/server.js b/asr-server/server.js new file mode 100644 index 0000000000..ed73ecb022 --- /dev/null +++ b/asr-server/server.js @@ -0,0 +1,269 @@ +// 检查依赖项 +try { + console.log('ASR Server starting...') + console.log('Node.js version:', process.version) + console.log('Current directory:', __dirname) + console.log('Current working directory:', process.cwd()) + console.log('Command line arguments:', process.argv) + + // 检查必要的依赖项 + const checkDependency = (name) => { + try { + require(name) // Removed unused variable 'module' + console.log(`Successfully loaded dependency: ${name}`) + return true + } catch (error) { + console.error(`Failed to load dependency: ${name}`, error.message) + return false + } + } + + // 检查所有必要的依赖项 + const dependencies = ['http', 'ws', 'express', 'path', 'fs'] + const missingDeps = dependencies.filter((dep) => !checkDependency(dep)) + + if (missingDeps.length > 0) { + console.error(`Missing dependencies: ${missingDeps.join(', ')}. Server cannot start.`) + process.exit(1) + } +} catch (error) { + console.error('Error during dependency check:', error) + process.exit(1) +} + +// 加载依赖项 +const http = require('http') +const WebSocket = require('ws') +const express = require('express') +const path = require('path') // Need path module +// const fs = require('fs') // Commented out unused import 'fs' + +const app = express() +const port = 34515 // Define the port + +// 获取index.html文件的路径 +function getIndexHtmlPath() { + const fs = require('fs') + console.log('Current directory:', __dirname) + console.log('Current working directory:', process.cwd()) + + // 尝试多个可能的路径 + const possiblePaths = [ + // 开发环境路径 + path.join(__dirname, 'index.html'), + // 当前目录 + path.join(process.cwd(), 'index.html'), + // 相对于可执行文件的路径 + path.join(path.dirname(process.execPath), 'index.html'), + // 相对于可执行文件的上级目录的路径 + path.join(path.dirname(path.dirname(process.execPath)), 'index.html'), + // 相对于可执行文件的resources目录的路径 + path.join(path.dirname(process.execPath), 'resources', 'index.html'), + // 相对于可执行文件的resources/asr-server目录的路径 + path.join(path.dirname(process.execPath), 'resources', 'asr-server', 'index.html'), + // 相对于可执行文件的asr-server目录的路径 + path.join(path.dirname(process.execPath), 'asr-server', 'index.html'), + // 如果是pkg打包环境 + process.pkg ? path.join(path.dirname(process.execPath), 'index.html') : null + ].filter(Boolean) // 过滤掉null值 + + console.log('Possible index.html paths:', possiblePaths) + + // 检查每个路径,返回第一个存在的文件 + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + console.log(`Found index.html at: ${p}`) + return p + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + // 如果没有找到文件,返回默认路径并记录错误 + console.error('Could not find index.html in any of the expected locations') + return path.join(__dirname, 'index.html') // 返回默认路径,即使它可能不存在 +} + +// 提供网页给浏览器 +app.get('/', (req, res) => { + try { + const indexPath = getIndexHtmlPath() + console.log(`Serving index.html from: ${indexPath}`) + + // 检查文件是否存在 + const fs = require('fs') + if (!fs.existsSync(indexPath)) { + console.error(`Error: index.html not found at ${indexPath}`) + return res.status(404).send(`Error: index.html not found at ${indexPath}.
Please check the server logs.`) + } + + res.sendFile(indexPath, (err) => { + if (err) { + console.error('Error sending index.html:', err) + res.status(500).send(`Error serving index.html: ${err.message}`) + } + }) + } catch (error) { + console.error('Error in route handler:', error) + res.status(500).send(`Server error: ${error.message}`) + } +}) + +const server = http.createServer(app) +const wss = new WebSocket.Server({ server }) + +let browserConnection = null +let electronConnection = null + +wss.on('connection', (ws) => { + console.log('[Server] WebSocket client connected') // Add log + + ws.on('message', (message) => { + let data + try { + // Ensure message is treated as string before parsing + data = JSON.parse(message.toString()) + console.log('[Server] Received message:', data) // Log parsed data + } catch (e) { + console.error('[Server] Failed to parse message or message is not JSON:', message.toString(), e) + return // Ignore non-JSON messages + } + + // 识别客户端类型 + if (data.type === 'identify') { + if (data.role === 'browser') { + browserConnection = ws + console.log('[Server] Browser identified and connected') + // Notify Electron that the browser is ready + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'browser_ready' })) + console.log('[Server] Sent browser_ready status to Electron') + } + // Notify Electron if it's already connected + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connected' })) + } + ws.on('close', () => { + console.log('[Server] Browser disconnected') + browserConnection = null + // Notify Electron + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser disconnected' })) + } + }) + ws.on('error', (error) => { + console.error('[Server] Browser WebSocket error:', error) + browserConnection = null // Assume disconnected on error + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connection error' })) + } + }) + } else if (data.role === 'electron') { + electronConnection = ws + console.log('[Server] Electron identified and connected') + // If browser is already connected when Electron connects, notify Electron immediately + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'browser_ready' })) + console.log('[Server] Sent initial browser_ready status to Electron') + } + ws.on('close', () => { + console.log('[Server] Electron disconnected') + electronConnection = null + // Maybe send stop to browser if electron disconnects? + // if (browserConnection) browserConnection.send(JSON.stringify({ type: 'stop' })); + }) + ws.on('error', (error) => { + console.error('[Server] Electron WebSocket error:', error) + electronConnection = null // Assume disconnected on error + }) + } + } + // Electron 控制开始/停止 + else if (data.type === 'start' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying START command to browser') + browserConnection.send(JSON.stringify({ type: 'start' })) + } else { + console.log('[Server] Cannot relay START: Browser not connected') + // Optionally notify Electron back + electronConnection.send(JSON.stringify({ type: 'error', message: 'Browser not connected for ASR' })) + } + } else if (data.type === 'stop' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying STOP command to browser') + browserConnection.send(JSON.stringify({ type: 'stop' })) + } else { + console.log('[Server] Cannot relay STOP: Browser not connected') + } + } else if (data.type === 'reset' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying RESET command to browser') + browserConnection.send(JSON.stringify({ type: 'reset' })) + } else { + console.log('[Server] Cannot relay RESET: Browser not connected') + } + } + // 浏览器发送识别结果 + else if (data.type === 'result' && ws === browserConnection) { + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + // console.log('[Server] Relaying RESULT to Electron:', data.data); // Log less frequently if needed + electronConnection.send(JSON.stringify({ type: 'result', data: data.data })) + } else { + // console.log('[Server] Cannot relay RESULT: Electron not connected'); + } + } + // 浏览器发送状态更新 (例如 'stopped') + else if (data.type === 'status' && ws === browserConnection) { + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying STATUS to Electron:', data.message) // Log status being relayed + electronConnection.send(JSON.stringify({ type: 'status', message: data.message })) + } else { + console.log('[Server] Cannot relay STATUS: Electron not connected') + } + } else { + console.log('[Server] Received unknown message type or from unknown source:', data) + } + }) + + ws.on('error', (error) => { + // Generic error handling for connection before identification + console.error('[Server] Initial WebSocket connection error:', error) + // Attempt to clean up based on which connection it might be (if identified) + if (ws === browserConnection) { + browserConnection = null + if (electronConnection) + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connection error' })) + } else if (ws === electronConnection) { + electronConnection = null + } + }) +}) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // Handle server errors + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/asr-server/standalone.js b/asr-server/standalone.js new file mode 100644 index 0000000000..7b826d1f25 --- /dev/null +++ b/asr-server/standalone.js @@ -0,0 +1,114 @@ +/** + * 独立的ASR服务器 + * 这个文件是一个简化版的server.js,用于在打包后的应用中运行 + */ + +// 基本依赖 +const http = require('http') +const express = require('express') +const path = require('path') +const fs = require('fs') + +// 输出环境信息 +console.log('ASR Server starting...') +console.log('Node.js version:', process.version) +console.log('Current directory:', __dirname) +console.log('Current working directory:', process.cwd()) +console.log('Command line arguments:', process.argv) + +// 创建Express应用 +const app = express() +const port = 34515 + +// 提供静态文件 +app.use(express.static(__dirname)) + +// 提供网页给浏览器 +app.get('/', (req, res) => { + try { + // 尝试多个可能的路径 + const possiblePaths = [ + // 当前目录 + path.join(__dirname, 'index.html'), + // 上级目录 + path.join(__dirname, '..', 'index.html'), + // 应用根目录 + path.join(process.cwd(), 'index.html') + ] + + console.log('Possible index.html paths:', possiblePaths) + + // 查找第一个存在的文件 + let indexPath = null + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + indexPath = p + console.log(`Found index.html at: ${p}`) + break + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + if (indexPath) { + res.sendFile(indexPath) + } else { + // 如果找不到文件,返回一个简单的HTML页面 + console.error('Could not find index.html, serving fallback page') + res.send(` + + + + ASR Server + + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:${port}

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } catch (error) { + console.error('Error serving index.html:', error) + res.status(500).send(`Server error: ${error.message}`) + } +}) + +// 创建HTTP服务器 +const server = http.createServer(app) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/asr-server/start-server.bat b/asr-server/start-server.bat new file mode 100644 index 0000000000..3f9628a75c --- /dev/null +++ b/asr-server/start-server.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting ASR Server... +cd /d %~dp0 +node standalone.js +pause diff --git a/electron-builder.yml b/electron-builder.yml index 29c8c97e47..962202f3b0 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -29,14 +29,18 @@ files: - '!node_modules/@tavily/core/node_modules/js-tiktoken' - '!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}' - '!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}' - # 包含 ASR 服务器文件 - - src/renderer/src/assets/asr-server/**/* - # 包含打包后的ASR服务器可执行文件 - - cherry-asr-server.exe - - index.html -asarUnpack: +asarUnpack: # Removed ASR server rules from 'files' section - resources/** - '**/*.{node,dll,metal,exp,lib}' +extraResources: # Add extraResources to copy the prepared asr-server directory + - from: asr-server # Copy the folder from project root + to: app/asr-server # Copy TO the 'app' subfolder within resources + filter: + - "**/*" # Include everything inside + - from: resources/data # Copy the data folder with agents.json + to: data # Copy TO the 'data' subfolder within resources + filter: + - "**/*" # Include everything inside win: executableName: Cherry Studio artifactName: ${productName}-${version}-${arch}-setup.${ext} diff --git a/index.html b/index.html new file mode 100644 index 0000000000..cfa0db82fb --- /dev/null +++ b/index.html @@ -0,0 +1,425 @@ + + + + + + + Cherry Studio ASR + + + + +

浏览器语音识别中继页面

+

这个页面需要在浏览器中保持打开,以便应用使用其语音识别功能。

+
正在连接到服务器...
+
+ + + + + \ No newline at end of file diff --git a/public/asr-server/embedded.js b/public/asr-server/embedded.js new file mode 100644 index 0000000000..68ae30e239 --- /dev/null +++ b/public/asr-server/embedded.js @@ -0,0 +1,123 @@ +/** + * 内置的ASR服务器模块 + * 这个文件可以直接在Electron中运行,不需要外部依赖 + */ + +// 使用Electron内置的Node.js模块 +const http = require('http') +const path = require('path') +const fs = require('fs') + +// 输出环境信息 +console.log('ASR Server (Embedded) starting...') +console.log('Node.js version:', process.version) +console.log('Current directory:', __dirname) +console.log('Current working directory:', process.cwd()) +console.log('Command line arguments:', process.argv) + +// 创建HTTP服务器 +const server = http.createServer((req, res) => { + try { + if (req.url === '/' || req.url === '/index.html') { + // 尝试多个可能的路径 + const possiblePaths = [ + // 当前目录 + path.join(__dirname, 'index.html'), + // 上级目录 + path.join(__dirname, '..', 'index.html'), + // 应用根目录 + path.join(process.cwd(), 'index.html') + ] + + console.log('Possible index.html paths:', possiblePaths) + + // 查找第一个存在的文件 + let indexPath = null + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + indexPath = p + console.log(`Found index.html at: ${p}`) + break + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + if (indexPath) { + // 读取文件内容并发送 + fs.readFile(indexPath, (err, data) => { + if (err) { + console.error('Error reading index.html:', err) + res.writeHead(500) + res.end('Error reading index.html') + return + } + + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(data) + }) + } else { + // 如果找不到文件,返回一个简单的HTML页面 + console.error('Could not find index.html, serving fallback page') + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(` + + + + ASR Server + + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:34515

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } else { + // 处理其他请求 + res.writeHead(404) + res.end('Not found') + } + } catch (error) { + console.error('Error handling request:', error) + res.writeHead(500) + res.end('Server error') + } +}) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + const port = 34515 + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/public/asr-server/index.html b/public/asr-server/index.html index 00a207f6de..cfa0db82fb 100644 --- a/public/asr-server/index.html +++ b/public/asr-server/index.html @@ -4,7 +4,7 @@ - Browser ASR (External) + Cherry Studio ASR + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:${port}

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } catch (error) { + console.error('Error serving index.html:', error) + res.status(500).send(`Server error: ${error.message}`) + } +}) + +// 创建HTTP服务器 +const server = http.createServer(app) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/public/asr-server/start-server.bat b/public/asr-server/start-server.bat new file mode 100644 index 0000000000..3f9628a75c --- /dev/null +++ b/public/asr-server/start-server.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting ASR Server... +cd /d %~dp0 +node standalone.js +pause diff --git a/resources/asr-server/embedded.js b/resources/asr-server/embedded.js new file mode 100644 index 0000000000..68ae30e239 --- /dev/null +++ b/resources/asr-server/embedded.js @@ -0,0 +1,123 @@ +/** + * 内置的ASR服务器模块 + * 这个文件可以直接在Electron中运行,不需要外部依赖 + */ + +// 使用Electron内置的Node.js模块 +const http = require('http') +const path = require('path') +const fs = require('fs') + +// 输出环境信息 +console.log('ASR Server (Embedded) starting...') +console.log('Node.js version:', process.version) +console.log('Current directory:', __dirname) +console.log('Current working directory:', process.cwd()) +console.log('Command line arguments:', process.argv) + +// 创建HTTP服务器 +const server = http.createServer((req, res) => { + try { + if (req.url === '/' || req.url === '/index.html') { + // 尝试多个可能的路径 + const possiblePaths = [ + // 当前目录 + path.join(__dirname, 'index.html'), + // 上级目录 + path.join(__dirname, '..', 'index.html'), + // 应用根目录 + path.join(process.cwd(), 'index.html') + ] + + console.log('Possible index.html paths:', possiblePaths) + + // 查找第一个存在的文件 + let indexPath = null + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + indexPath = p + console.log(`Found index.html at: ${p}`) + break + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + if (indexPath) { + // 读取文件内容并发送 + fs.readFile(indexPath, (err, data) => { + if (err) { + console.error('Error reading index.html:', err) + res.writeHead(500) + res.end('Error reading index.html') + return + } + + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(data) + }) + } else { + // 如果找不到文件,返回一个简单的HTML页面 + console.error('Could not find index.html, serving fallback page') + res.writeHead(200, { 'Content-Type': 'text/html' }) + res.end(` + + + + ASR Server + + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:34515

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } else { + // 处理其他请求 + res.writeHead(404) + res.end('Not found') + } + } catch (error) { + console.error('Error handling request:', error) + res.writeHead(500) + res.end('Server error') + } +}) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + const port = 34515 + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/resources/asr-server/index.html b/resources/asr-server/index.html new file mode 100644 index 0000000000..cfa0db82fb --- /dev/null +++ b/resources/asr-server/index.html @@ -0,0 +1,425 @@ + + + + + + + Cherry Studio ASR + + + + +

浏览器语音识别中继页面

+

这个页面需要在浏览器中保持打开,以便应用使用其语音识别功能。

+
正在连接到服务器...
+
+ + + + + \ No newline at end of file diff --git a/resources/asr-server/package-lock.json b/resources/asr-server/package-lock.json new file mode 100644 index 0000000000..8d3eb4035d --- /dev/null +++ b/resources/asr-server/package-lock.json @@ -0,0 +1,854 @@ +{ + "name": "cherry-asr-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cherry-asr-server", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.13.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/resources/asr-server/package.json b/resources/asr-server/package.json new file mode 100644 index 0000000000..b09528cca8 --- /dev/null +++ b/resources/asr-server/package.json @@ -0,0 +1,10 @@ +{ + "name": "cherry-asr-server", + "version": "1.0.0", + "description": "Cherry Studio ASR Server", + "main": "server.js", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.13.0" + } +} \ No newline at end of file diff --git a/resources/asr-server/server.js b/resources/asr-server/server.js new file mode 100644 index 0000000000..ed73ecb022 --- /dev/null +++ b/resources/asr-server/server.js @@ -0,0 +1,269 @@ +// 检查依赖项 +try { + console.log('ASR Server starting...') + console.log('Node.js version:', process.version) + console.log('Current directory:', __dirname) + console.log('Current working directory:', process.cwd()) + console.log('Command line arguments:', process.argv) + + // 检查必要的依赖项 + const checkDependency = (name) => { + try { + require(name) // Removed unused variable 'module' + console.log(`Successfully loaded dependency: ${name}`) + return true + } catch (error) { + console.error(`Failed to load dependency: ${name}`, error.message) + return false + } + } + + // 检查所有必要的依赖项 + const dependencies = ['http', 'ws', 'express', 'path', 'fs'] + const missingDeps = dependencies.filter((dep) => !checkDependency(dep)) + + if (missingDeps.length > 0) { + console.error(`Missing dependencies: ${missingDeps.join(', ')}. Server cannot start.`) + process.exit(1) + } +} catch (error) { + console.error('Error during dependency check:', error) + process.exit(1) +} + +// 加载依赖项 +const http = require('http') +const WebSocket = require('ws') +const express = require('express') +const path = require('path') // Need path module +// const fs = require('fs') // Commented out unused import 'fs' + +const app = express() +const port = 34515 // Define the port + +// 获取index.html文件的路径 +function getIndexHtmlPath() { + const fs = require('fs') + console.log('Current directory:', __dirname) + console.log('Current working directory:', process.cwd()) + + // 尝试多个可能的路径 + const possiblePaths = [ + // 开发环境路径 + path.join(__dirname, 'index.html'), + // 当前目录 + path.join(process.cwd(), 'index.html'), + // 相对于可执行文件的路径 + path.join(path.dirname(process.execPath), 'index.html'), + // 相对于可执行文件的上级目录的路径 + path.join(path.dirname(path.dirname(process.execPath)), 'index.html'), + // 相对于可执行文件的resources目录的路径 + path.join(path.dirname(process.execPath), 'resources', 'index.html'), + // 相对于可执行文件的resources/asr-server目录的路径 + path.join(path.dirname(process.execPath), 'resources', 'asr-server', 'index.html'), + // 相对于可执行文件的asr-server目录的路径 + path.join(path.dirname(process.execPath), 'asr-server', 'index.html'), + // 如果是pkg打包环境 + process.pkg ? path.join(path.dirname(process.execPath), 'index.html') : null + ].filter(Boolean) // 过滤掉null值 + + console.log('Possible index.html paths:', possiblePaths) + + // 检查每个路径,返回第一个存在的文件 + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + console.log(`Found index.html at: ${p}`) + return p + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + // 如果没有找到文件,返回默认路径并记录错误 + console.error('Could not find index.html in any of the expected locations') + return path.join(__dirname, 'index.html') // 返回默认路径,即使它可能不存在 +} + +// 提供网页给浏览器 +app.get('/', (req, res) => { + try { + const indexPath = getIndexHtmlPath() + console.log(`Serving index.html from: ${indexPath}`) + + // 检查文件是否存在 + const fs = require('fs') + if (!fs.existsSync(indexPath)) { + console.error(`Error: index.html not found at ${indexPath}`) + return res.status(404).send(`Error: index.html not found at ${indexPath}.
Please check the server logs.`) + } + + res.sendFile(indexPath, (err) => { + if (err) { + console.error('Error sending index.html:', err) + res.status(500).send(`Error serving index.html: ${err.message}`) + } + }) + } catch (error) { + console.error('Error in route handler:', error) + res.status(500).send(`Server error: ${error.message}`) + } +}) + +const server = http.createServer(app) +const wss = new WebSocket.Server({ server }) + +let browserConnection = null +let electronConnection = null + +wss.on('connection', (ws) => { + console.log('[Server] WebSocket client connected') // Add log + + ws.on('message', (message) => { + let data + try { + // Ensure message is treated as string before parsing + data = JSON.parse(message.toString()) + console.log('[Server] Received message:', data) // Log parsed data + } catch (e) { + console.error('[Server] Failed to parse message or message is not JSON:', message.toString(), e) + return // Ignore non-JSON messages + } + + // 识别客户端类型 + if (data.type === 'identify') { + if (data.role === 'browser') { + browserConnection = ws + console.log('[Server] Browser identified and connected') + // Notify Electron that the browser is ready + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'browser_ready' })) + console.log('[Server] Sent browser_ready status to Electron') + } + // Notify Electron if it's already connected + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connected' })) + } + ws.on('close', () => { + console.log('[Server] Browser disconnected') + browserConnection = null + // Notify Electron + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser disconnected' })) + } + }) + ws.on('error', (error) => { + console.error('[Server] Browser WebSocket error:', error) + browserConnection = null // Assume disconnected on error + if (electronConnection) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connection error' })) + } + }) + } else if (data.role === 'electron') { + electronConnection = ws + console.log('[Server] Electron identified and connected') + // If browser is already connected when Electron connects, notify Electron immediately + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + electronConnection.send(JSON.stringify({ type: 'status', message: 'browser_ready' })) + console.log('[Server] Sent initial browser_ready status to Electron') + } + ws.on('close', () => { + console.log('[Server] Electron disconnected') + electronConnection = null + // Maybe send stop to browser if electron disconnects? + // if (browserConnection) browserConnection.send(JSON.stringify({ type: 'stop' })); + }) + ws.on('error', (error) => { + console.error('[Server] Electron WebSocket error:', error) + electronConnection = null // Assume disconnected on error + }) + } + } + // Electron 控制开始/停止 + else if (data.type === 'start' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying START command to browser') + browserConnection.send(JSON.stringify({ type: 'start' })) + } else { + console.log('[Server] Cannot relay START: Browser not connected') + // Optionally notify Electron back + electronConnection.send(JSON.stringify({ type: 'error', message: 'Browser not connected for ASR' })) + } + } else if (data.type === 'stop' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying STOP command to browser') + browserConnection.send(JSON.stringify({ type: 'stop' })) + } else { + console.log('[Server] Cannot relay STOP: Browser not connected') + } + } else if (data.type === 'reset' && ws === electronConnection) { + if (browserConnection && browserConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying RESET command to browser') + browserConnection.send(JSON.stringify({ type: 'reset' })) + } else { + console.log('[Server] Cannot relay RESET: Browser not connected') + } + } + // 浏览器发送识别结果 + else if (data.type === 'result' && ws === browserConnection) { + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + // console.log('[Server] Relaying RESULT to Electron:', data.data); // Log less frequently if needed + electronConnection.send(JSON.stringify({ type: 'result', data: data.data })) + } else { + // console.log('[Server] Cannot relay RESULT: Electron not connected'); + } + } + // 浏览器发送状态更新 (例如 'stopped') + else if (data.type === 'status' && ws === browserConnection) { + if (electronConnection && electronConnection.readyState === WebSocket.OPEN) { + console.log('[Server] Relaying STATUS to Electron:', data.message) // Log status being relayed + electronConnection.send(JSON.stringify({ type: 'status', message: data.message })) + } else { + console.log('[Server] Cannot relay STATUS: Electron not connected') + } + } else { + console.log('[Server] Received unknown message type or from unknown source:', data) + } + }) + + ws.on('error', (error) => { + // Generic error handling for connection before identification + console.error('[Server] Initial WebSocket connection error:', error) + // Attempt to clean up based on which connection it might be (if identified) + if (ws === browserConnection) { + browserConnection = null + if (electronConnection) + electronConnection.send(JSON.stringify({ type: 'status', message: 'Browser connection error' })) + } else if (ws === electronConnection) { + electronConnection = null + } + }) +}) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // Handle server errors + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/resources/asr-server/standalone.js b/resources/asr-server/standalone.js new file mode 100644 index 0000000000..7b826d1f25 --- /dev/null +++ b/resources/asr-server/standalone.js @@ -0,0 +1,114 @@ +/** + * 独立的ASR服务器 + * 这个文件是一个简化版的server.js,用于在打包后的应用中运行 + */ + +// 基本依赖 +const http = require('http') +const express = require('express') +const path = require('path') +const fs = require('fs') + +// 输出环境信息 +console.log('ASR Server starting...') +console.log('Node.js version:', process.version) +console.log('Current directory:', __dirname) +console.log('Current working directory:', process.cwd()) +console.log('Command line arguments:', process.argv) + +// 创建Express应用 +const app = express() +const port = 34515 + +// 提供静态文件 +app.use(express.static(__dirname)) + +// 提供网页给浏览器 +app.get('/', (req, res) => { + try { + // 尝试多个可能的路径 + const possiblePaths = [ + // 当前目录 + path.join(__dirname, 'index.html'), + // 上级目录 + path.join(__dirname, '..', 'index.html'), + // 应用根目录 + path.join(process.cwd(), 'index.html') + ] + + console.log('Possible index.html paths:', possiblePaths) + + // 查找第一个存在的文件 + let indexPath = null + for (const p of possiblePaths) { + try { + if (fs.existsSync(p)) { + indexPath = p + console.log(`Found index.html at: ${p}`) + break + } + } catch (e) { + console.error(`Error checking existence of ${p}:`, e) + } + } + + if (indexPath) { + res.sendFile(indexPath) + } else { + // 如果找不到文件,返回一个简单的HTML页面 + console.error('Could not find index.html, serving fallback page') + res.send(` + + + + ASR Server + + + +

ASR Server is running

+

This is a fallback page because the index.html file could not be found.

+

Server is running at: http://localhost:${port}

+

Current directory: ${__dirname}

+

Working directory: ${process.cwd()}

+ + + `) + } + } catch (error) { + console.error('Error serving index.html:', error) + res.status(500).send(`Server error: ${error.message}`) + } +}) + +// 创建HTTP服务器 +const server = http.createServer(app) + +// 添加进程错误处理 +process.on('uncaughtException', (error) => { + console.error('[Server] Uncaught exception:', error) + // 不立即退出,给日志输出的时间 + setTimeout(() => process.exit(1), 1000) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason) +}) + +// 尝试启动服务器 +try { + server.listen(port, () => { + console.log(`[Server] Server running at http://localhost:${port}`) + }) + + // 处理服务器错误 + server.on('error', (error) => { + console.error(`[Server] Failed to start server:`, error) + process.exit(1) // Exit if server fails to start + }) +} catch (error) { + console.error('[Server] Critical error starting server:', error) + process.exit(1) +} diff --git a/resources/asr-server/start-server.bat b/resources/asr-server/start-server.bat new file mode 100644 index 0000000000..3f9628a75c --- /dev/null +++ b/resources/asr-server/start-server.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting ASR Server... +cd /d %~dp0 +node standalone.js +pause diff --git a/src/main/services/ASRServerService.ts b/src/main/services/ASRServerService.ts index 1aec4c8498..52fa7dd4bd 100644 --- a/src/main/services/ASRServerService.ts +++ b/src/main/services/ASRServerService.ts @@ -37,21 +37,21 @@ class ASRServerService { log.info('App path:', app.getAppPath()) // 在开发环境和生产环境中使用不同的路径 let serverPath = '' - let isExeFile = false + const isPackaged = app.isPackaged - // 首先检查是否有打包后的exe文件 - const exePath = path.join(app.getAppPath(), 'resources', 'cherry-asr-server.exe') - if (fs.existsSync(exePath)) { - serverPath = exePath - isExeFile = true - log.info('检测到打包后的exe文件:', serverPath) - } else if (process.env.NODE_ENV === 'development') { - // 开发环境 - serverPath = path.join(app.getAppPath(), 'src', 'renderer', 'src', 'assets', 'asr-server', 'server.js') + if (isPackaged) { + // 生产环境 (打包后) - 使用 extraResources 复制的路径 + // 注意: 'app' 是 extraResources 配置中 'to' 字段的一部分 + serverPath = path.join(process.resourcesPath, 'app', 'asr-server', 'server.js') + log.info('生产环境,ASR 服务器路径:', serverPath) } else { - // 生产环境 - serverPath = path.join(app.getAppPath(), 'public', 'asr-server', 'server.js') + // 开发环境 - 指向项目根目录的 asr-server + serverPath = path.join(app.getAppPath(), 'asr-server', 'server.js') + log.info('开发环境,ASR 服务器路径:', serverPath) } + + // 注意:删除了 isExeFile 检查逻辑, 假设总是用 node 启动 + // Removed unused variable 'isExeFile' log.info('ASR服务器路径:', serverPath) // 检查文件是否存在 @@ -60,19 +60,12 @@ class ASRServerService { } // 启动服务器进程 - if (isExeFile) { - // 如果是exe文件,直接启动 - this.asrServerProcess = spawn(serverPath, [], { - stdio: 'pipe', - detached: false - }) - } else { - // 如果是js文件,使用node启动 - this.asrServerProcess = spawn('node', [serverPath], { - stdio: 'pipe', - detached: false - }) - } + // 始终使用 node 启动 server.js + log.info(`尝试使用 node 启动: ${serverPath}`) + this.asrServerProcess = spawn('node', [serverPath], { + stdio: 'pipe', // 'pipe' 用于捕获输出, 如果需要调试可以临时改为 'inherit' + detached: false // false 通常足够 + }) // 处理服务器输出 this.asrServerProcess.stdout?.on('data', (data) => { diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts index 4a6fde670d..9e5b7b8c5d 100644 --- a/src/main/utils/index.ts +++ b/src/main/utils/index.ts @@ -4,7 +4,8 @@ import path from 'node:path' import { app } from 'electron' export function getResourcePath() { - return path.join(app.getAppPath(), 'resources') + // 在打包环境中,使用process.resourcesPath,否则使用app.getAppPath()/resources + return app.isPackaged ? process.resourcesPath : path.join(app.getAppPath(), 'resources') } export function getDataPath() { diff --git a/src/renderer/src/assets/asr-server/server.js b/src/renderer/src/assets/asr-server/server.js index 677124ce97..0840ec619d 100644 --- a/src/renderer/src/assets/asr-server/server.js +++ b/src/renderer/src/assets/asr-server/server.js @@ -4,7 +4,7 @@ const express = require('express') const path = require('path') // Need path module const app = express() -const port = 8080 // Define the port +const port = 34515 // Define the port // 获取index.html文件的路径 function getIndexHtmlPath() { diff --git a/src/renderer/src/components/TTSProgressBar.tsx b/src/renderer/src/components/TTSProgressBar.tsx index 0a36e5e068..609cb6ff00 100644 --- a/src/renderer/src/components/TTSProgressBar.tsx +++ b/src/renderer/src/components/TTSProgressBar.tsx @@ -1,4 +1,6 @@ +import { RootState } from '@renderer/store' import React, { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' import styled from 'styled-components' interface TTSProgressBarProps { @@ -13,6 +15,9 @@ interface TTSProgressState { } const TTSProgressBar: React.FC = ({ messageId }) => { + // 获取是否显示TTS进度条的设置 + const showTTSProgressBar = useSelector((state: RootState) => state.settings.showTTSProgressBar) + const [progressState, setProgressState] = useState({ isPlaying: false, progress: 0, @@ -20,22 +25,40 @@ const TTSProgressBar: React.FC = ({ messageId }) => { duration: 0 }) + // 添加拖动状态 + const [isDragging, setIsDragging] = useState(false) + // 监听TTS进度更新事件 useEffect(() => { const handleProgressUpdate = (event: CustomEvent) => { const { messageId: playingMessageId, isPlaying, progress, currentTime, duration } = event.detail - console.log('TTS进度更新事件:', { - playingMessageId, - currentMessageId: messageId, - isPlaying, - progress, - currentTime, - duration - }) + // 不需要每次都输出日志,避免控制台刷屏 + // 只在进度变化较大时输出日志,或者开始/结束时 + // 在拖动进度条时不输出日志 + // 完全关闭进度更新日志输出 + // if (!isDragging && + // playingMessageId === messageId && + // ( + // // 开始或结束播放 + // (isPlaying !== progressState.isPlaying) || + // // 每10%输出一次日志 + // (Math.floor(progress / 10) !== Math.floor(progressState.progress / 10)) + // ) + // ) { + // console.log('TTS进度更新:', { + // messageId: messageId.substring(0, 8), + // isPlaying, + // progress: Math.round(progress), + // currentTime: Math.round(currentTime), + // duration: Math.round(duration) + // }) + // } // 只有当前消息正在播放时才更新进度 - if (playingMessageId === messageId) { + // 增加对playingMessageId的检查,确保它存在且不为空 + // 这样在语音通话模式下的开场白不会显示进度条 + if (playingMessageId && playingMessageId === messageId) { // 如果收到的是重置信号(duration为0),则强制设置为非播放状态 if (duration === 0 && currentTime === 0 && progress === 0) { setProgressState({ @@ -64,7 +87,7 @@ const TTSProgressBar: React.FC = ({ messageId }) => { // 如果停止播放,重置进度条状态 if (!isPlaying && progressState.isPlaying) { - console.log('收到TTS停止播放事件,重置进度条') + // console.log('收到TTS停止播放事件,重置进度条') setProgressState({ isPlaying: false, progress: 0, @@ -78,18 +101,18 @@ const TTSProgressBar: React.FC = ({ messageId }) => { window.addEventListener('tts-progress-update', handleProgressUpdate as EventListener) window.addEventListener('tts-state-change', handleStateChange as EventListener) - console.log('添加TTS进度更新事件监听器,消息ID:', messageId) + // console.log('添加TTS进度更新事件监听器,消息ID:', messageId) // 组件卸载时移除事件监听器 return () => { window.removeEventListener('tts-progress-update', handleProgressUpdate as EventListener) window.removeEventListener('tts-state-change', handleStateChange as EventListener) - console.log('移除TTS进度更新事件监听器,消息ID:', messageId) + // console.log('移除TTS进度更新事件监听器,消息ID:', messageId) } - }, [messageId, progressState.isPlaying]) + }, [messageId, progressState.isPlaying, isDragging]) - // 如果没有播放,不显示进度条 - if (!progressState.isPlaying) { + // 如果没有播放或者设置为不显示进度条,则不显示 + if (!progressState.isPlaying || !showTTSProgressBar) { return null } @@ -106,7 +129,7 @@ const TTSProgressBar: React.FC = ({ messageId }) => { const seekPercentage = (clickPosition / trackWidth) * 100 const seekTime = (seekPercentage / 100) * progressState.duration - console.log(`进度条点击: ${seekPercentage.toFixed(2)}%, 时间: ${seekTime.toFixed(2)}秒`) + // console.log(`进度条点击: ${seekPercentage.toFixed(2)}%, 时间: ${seekTime.toFixed(2)}秒`) // 调用TTS服务的seek方法 import('@renderer/services/TTSService').then(({ default: TTSService }) => { @@ -120,8 +143,8 @@ const TTSProgressBar: React.FC = ({ messageId }) => { e.preventDefault() e.stopPropagation() // 阻止事件冒泡 - // 记录开始拖动状态 - let isDragging = true + // 设置拖动状态为true + setIsDragging(true) const trackRect = e.currentTarget.getBoundingClientRect() const trackWidth = trackRect.width @@ -145,7 +168,8 @@ const TTSProgressBar: React.FC = ({ messageId }) => { const handleMouseUp = (upEvent: MouseEvent) => { if (!isDragging) return - isDragging = false + // 设置拖动状态为false + setIsDragging(false) document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) @@ -153,7 +177,7 @@ const TTSProgressBar: React.FC = ({ messageId }) => { const seekPercentage = (dragPosition / trackWidth) * 100 const seekTime = (seekPercentage / 100) * progressState.duration - console.log(`拖动结束: ${seekPercentage.toFixed(2)}%, 时间: ${seekTime.toFixed(2)}秒`) + // console.log(`拖动结束: ${seekPercentage.toFixed(2)}%, 时间: ${seekTime.toFixed(2)}秒`) // 调用TTS服务的seek方法 import('@renderer/services/TTSService').then(({ default: TTSService }) => { diff --git a/src/renderer/src/config/prompts.ts b/src/renderer/src/config/prompts.ts index fc24387af6..e1ed898d8f 100644 --- a/src/renderer/src/config/prompts.ts +++ b/src/renderer/src/config/prompts.ts @@ -1,5 +1,66 @@ +import i18n from '@renderer/i18n' import dayjs from 'dayjs' +// 语音通话提示词(多语言支持) +export const VOICE_CALL_PROMPTS: Record = { + 'zh-CN': `当前是语音通话模式。请注意: +1. 简洁直接地回答问题,避免冗长的引导和总结。 +2. 避免使用复杂的格式化内容,如表格、代码块、Markdown等。 +3. 使用自然、口语化的表达方式,就像与人对话一样。 +4. 如果需要列出要点,使用简单的数字或文字标记,而不是复杂的格式。 +5. 回答应该简短有力,便于用户通过语音理解。 +6. 避免使用特殊符号、表情符号、标点符号等,因为这些在语音播放时会影响理解。 +7. 使用完整的句子而非简单的关键词列表。 +8. 尽量使用常见词汇,避免生僻或专业术语,除非用户特别询问。`, + 'en-US': `This is voice call mode. Please note: +1. Answer questions concisely and directly, avoiding lengthy introductions and summaries. +2. Avoid complex formatted content such as tables, code blocks, Markdown, etc. +3. Use natural, conversational language as if speaking to a person. +4. If you need to list points, use simple numbers or text markers rather than complex formats. +5. Responses should be brief and powerful, easy for users to understand through voice. +6. Avoid special symbols, emojis, punctuation marks, etc., as these can affect comprehension during voice playback. +7. Use complete sentences rather than simple keyword lists. +8. Try to use common vocabulary, avoiding obscure or technical terms unless specifically asked by the user.`, + 'zh-TW': `當前是語音通話模式。請注意: +1. 簡潔直接地回答問題,避免冗長的引導和總結。 +2. 避免使用複雜的格式化內容,如表格、代碼塊、Markdown等。 +3. 使用自然、口語化的表達方式,就像與人對話一樣。 +4. 如果需要列出要點,使用簡單的數字或文字標記,而不是複雜的格式。 +5. 回答應該簡短有力,便於用戶通過語音理解。 +6. 避免使用特殊符號、表情符號、標點符號等,因為這些在語音播放時會影響理解。 +7. 使用完整的句子而非簡單的關鍵詞列表。 +8. 盡量使用常見詞彙,避免生僻或專業術語,除非用戶特別詢問。`, + 'ja-JP': `これは音声通話モードです。ご注意ください: +1. 質問に簡潔かつ直接的に答え、長い導入や要約を避けてください。 +2. 表、コードブロック、Markdownなどの複雑な書式付きコンテンツを避けてください。 +3. 人と話すように、自然で会話的な言葉を使ってください。 +4. ポイントをリストアップする必要がある場合は、複雑な形式ではなく、単純な数字やテキストマーカーを使用してください。 +5. 応答は簡潔で力強く、ユーザーが音声で理解しやすいものにしてください。 +6. 特殊記号、絵文字、句読点などは、音声再生中に理解に影響を与える可能性があるため、避けてください。 +7. 単純なキーワードリストではなく、完全な文を使用してください。 +8. ユーザーから特に質問されない限り、わかりにくい専門用語を避け、一般的な語彙を使用するようにしてください。`, + 'ru-RU': `Это режим голосового вызова. Обратите внимание: +1. Отвечайте на вопросы кратко и прямо, избегая длинных введений и резюме. +2. Избегайте сложного форматированного содержания, такого как таблицы, блоки кода, Markdown и т.д. +3. Используйте естественный, разговорный язык, как при разговоре с человеком. +4. Если вам нужно перечислить пункты, используйте простые цифры или текстовые маркеры, а не сложные форматы. +5. Ответы должны быть краткими и содержательными, легкими для понимания пользователем через голос. +6. Избегайте специальных символов, эмодзи, знаков препинания и т.д., так как они могут затруднить понимание при воспроизведении голосом. +7. Используйте полные предложения, а не простые списки ключевых слов. +8. Старайтесь использовать общеупотребительную лексику, избегая малоизвестных или технических терминов, если пользователь специально не спрашивает о них.` + // 可以添加更多语言... +} + +// 获取当前语言的默认语音通话提示词 +export function getDefaultVoiceCallPrompt(): string { + const language = i18n.language || 'en-US' + // 如果没有对应语言的提示词,使用英文提示词作为后备 + return VOICE_CALL_PROMPTS[language] || VOICE_CALL_PROMPTS['en-US'] +} + +// 为了向后兼容,保留原来的常量 +export const DEFAULT_VOICE_CALL_PROMPT = getDefaultVoiceCallPrompt() + export const AGENT_PROMPT = ` You are a Prompt Generator. You will integrate user input information into a structured Prompt using Markdown syntax. Please do not use code blocks for output, display directly! diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 8845d98e74..1a3d07362a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1418,7 +1418,9 @@ "filter.markdown": "Filter Markdown", "filter.code_blocks": "Filter code blocks", "filter.html_tags": "Filter HTML tags", + "filter.emojis": "Filter emojis", "max_text_length": "Maximum text length", + "show_progress_bar": "Show TTS progress bar", "test": "Test Speech", "help": "Text-to-speech functionality supports converting text to natural-sounding speech.", "learn_more": "Learn more", @@ -1499,7 +1501,9 @@ "transcribe_failed": "Failed to transcribe speech", "no_api_key": "[to be translated]:未设置API密钥", "browser_not_support": "[to be translated]:浏览器不支持语音识别" - } + }, + "auto_start_server": "[to be translated]:启动应用自动开启服务器", + "auto_start_server.help": "[to be translated]:启用后,应用启动时会自动开启语音识别服务器" }, "voice": { "title": "Voice Features", @@ -1514,6 +1518,16 @@ "model.select": "Select Model", "model.current": "Current Model: {{model}}", "model.info": "Select the AI model for voice calls. Different models may provide different voice interaction experiences", + "welcome_message": "Hello, I'm your AI assistant. Please press and hold the talk button to start a conversation.", + "prompt": { + "label": "Voice Call Prompt", + "placeholder": "Enter voice call prompt", + "save": "Save", + "reset": "Reset", + "saved": "Prompt saved", + "reset_done": "Prompt reset", + "info": "This prompt will guide the AI's responses in voice call mode" + }, "asr_tts_info": "Voice call uses the Speech Recognition (ASR) and Text-to-Speech (TTS) settings above", "test": "Test Voice Call", "test_info": "Please use the voice call button on the right side of the input box to test" diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index d5bcec3ee6..d607690b20 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1362,66 +1362,74 @@ "error": { "not_enabled": "音声合成が有効になっていません", "no_edge_voice": "ブラウザ TTSの音声が選択されていません", - "no_api_key": "[to be translated]:未设置API密钥", - "browser_not_support": "[to be translated]:浏览器不支持语音合成", - "no_voice": "[to be translated]:未选择音色", - "no_model": "[to be translated]:未选择模型", - "synthesis_failed": "[to be translated]:语音合成失败", - "play_failed": "[to be translated]:语音播放失败", - "empty_text": "[to be translated]:文本为空", - "general": "[to be translated]:语音合成出现错误", - "unsupported_service_type": "[to be translated]:不支持的服务类型: {{serviceType}}" + "no_api_key": "APIキーが設定されていません", + "browser_not_support": "ブラウザが音声合成をサポートしていません", + "no_voice": "音声が選択されていません", + "no_model": "モデルが選択されていません", + "synthesis_failed": "音声合成に失敗しました", + "play_failed": "音声再生に失敗しました", + "empty_text": "テキストが空です", + "general": "音声合成エラーが発生しました", + "unsupported_service_type": "サポートされていないサービスタイプ: {{serviceType}}" }, "help": "OpenAIのTTS APIを使用するには、APIキーが必要です。ブラウザ TTSはブラウザの機能を使用するため、APIキーは不要です。", "learn_more": "詳細はこちら", - "tab_title": "[to be translated]:语音合成", - "service_type.refresh": "[to be translated]:刷新TTS服务类型设置", - "service_type.refreshed": "[to be translated]:已刷新TTS服务类型设置", - "api_key": "[to be translated]:API密钥", - "api_key.placeholder": "[to be translated]:请输入OpenAI API密钥", - "api_url": "[to be translated]:API地址", - "api_url.placeholder": "[to be translated]:例如:https://api.openai.com/v1/audio/speech", - "edge_voice": "[to be translated]:浏览器 TTS音色", - "edge_voice.loading": "[to be translated]:加载中...", - "edge_voice.refresh": "[to be translated]:刷新可用音色列表", - "edge_voice.not_found": "[to be translated]:未找到匹配的音色", - "voice": "[to be translated]:音色", - "voice.placeholder": "[to be translated]:请选择音色", - "voice_input_placeholder": "[to be translated]:输入音色", - "voice_add": "[to be translated]:添加", - "voice_empty": "[to be translated]:暂无自定义音色,请在下方添加", - "model": "[to be translated]:模型", - "model.placeholder": "[to be translated]:请选择模型", - "model_input_placeholder": "[to be translated]:输入模型", - "model_add": "[to be translated]:添加", - "model_empty": "[to be translated]:暂无自定义模型,请在下方添加", - "filter_options": "[to be translated]:过滤选项", - "filter.thinking_process": "[to be translated]:过滤思考过程", - "filter.markdown": "[to be translated]:过滤Markdown标记", - "filter.code_blocks": "[to be translated]:过滤代码块", - "filter.html_tags": "[to be translated]:过滤HTML标签", - "max_text_length": "[to be translated]:最大文本长度", - "service_type.siliconflow": "[to be translated]:硅基流动", - "service_type.mstts": "[to be translated]:免费在线 TTS", - "siliconflow_api_key": "[to be translated]:硅基流动API密钥", - "siliconflow_api_key.placeholder": "[to be translated]:请输入硅基流动API密钥", - "siliconflow_api_url": "[to be translated]:硅基流动API地址", - "siliconflow_api_url.placeholder": "[to be translated]:例如:https://api.siliconflow.cn/v1/audio/speech", - "siliconflow_voice": "[to be translated]:硅基流动音色", - "siliconflow_voice.placeholder": "[to be translated]:请选择音色", - "siliconflow_model": "[to be translated]:硅基流动模型", - "siliconflow_model.placeholder": "[to be translated]:请选择模型", - "siliconflow_response_format": "[to be translated]:响应格式", - "siliconflow_response_format.placeholder": "[to be translated]:默认为mp3", - "siliconflow_speed": "[to be translated]:语速", - "siliconflow_speed.placeholder": "[to be translated]:默认为1.0", - "edge_voice.available_count": "[to be translated]:可用语音: {{count}}个", - "edge_voice.refreshing": "[to be translated]:正在刷新语音列表...", - "edge_voice.refreshed": "[to be translated]:语音列表已刷新", - "mstts.voice": "[to be translated]:免费在线 TTS音色", - "mstts.output_format": "[to be translated]:输出格式", - "mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。", - "error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色" + "tab_title": "音声合成", + "service_type.refresh": "TTS サービスタイプ設定を更新", + "service_type.refreshed": "TTS サービスタイプ設定が更新されました", + "api_key": "API キー", + "api_key.placeholder": "OpenAI API キーを入力してください", + "api_url": "API アドレス", + "api_url.placeholder": "例:https://api.openai.com/v1/audio/speech", + "edge_voice": "ブラウザ TTS 音声", + "edge_voice.loading": "読み込み中...", + "edge_voice.refresh": "利用可能な音声リストを更新", + "edge_voice.not_found": "一致する音声が見つかりません", + "voice": "音声", + "voice.placeholder": "音声を選択してください", + "voice_input_placeholder": "音声を入力", + "voice_add": "追加", + "voice_empty": "カスタム音声がありません。下に追加してください", + "model": "モデル", + "model.placeholder": "モデルを選択してください", + "model_input_placeholder": "モデルを入力", + "model_add": "追加", + "model_empty": "カスタムモデルがありません。下に追加してください", + "filter_options": "フィルターオプション", + "filter.thinking_process": "思考プロセスをフィルター", + "filter.markdown": "Markdownタグをフィルター", + "filter.code_blocks": "コードブロックをフィルター", + "filter.html_tags": "HTMLタグをフィルター", + "max_text_length": "最大テキスト長", + "service_type.siliconflow": "シリコンフロー", + "service_type.mstts": "無料オンライン TTS", + "siliconflow_api_key": "シリコンフロー API キー", + "siliconflow_api_key.placeholder": "シリコンフロー API キーを入力してください", + "siliconflow_api_url": "シリコンフロー API アドレス", + "siliconflow_api_url.placeholder": "例:https://api.siliconflow.cn/v1/audio/speech", + "siliconflow_voice": "シリコンフロー音声", + "siliconflow_voice.placeholder": "音声を選択してください", + "siliconflow_model": "シリコンフローモデル", + "siliconflow_model.placeholder": "モデルを選択してください", + "siliconflow_response_format": "レスポンス形式", + "siliconflow_response_format.placeholder": "デフォルトはmp3", + "siliconflow_speed": "話す速度", + "siliconflow_speed.placeholder": "デフォルトは1.0", + "edge_voice.available_count": "利用可能な音声: {{count}}個", + "edge_voice.refreshing": "音声リストを更新中...", + "edge_voice.refreshed": "音声リストが更新されました", + "mstts.voice": "無料オンライン TTS 音声", + "mstts.output_format": "出力形式", + "mstts.info": "無料オンラインTTSサービスはAPIキーが不要で、完全に無料で使用できます。", + "error.no_mstts_voice": "無料オンライン TTS 音声が設定されていません", + "play": "音声を再生", + "stop": "再生を停止", + "speak": "音声を再生", + "stop_global": "すべての音声再生を停止", + "stopped": "音声再生を停止しました", + "segmented": "分割", + "segmented_play": "分割再生", + "segmented_playback": "分割再生" }, "asr": { "title": "音声認識", @@ -1467,26 +1475,40 @@ "not_enabled": "音声認識が有効になっていません", "start_failed": "録音の開始に失敗しました", "transcribe_failed": "音声の文字起こしに失敗しました", - "no_api_key": "[to be translated]:未设置API密钥", - "browser_not_support": "[to be translated]:浏览器不支持语音识别" - } + "no_api_key": "APIキーが設定されていません", + "browser_not_support": "ブラウザが音声認識をサポートしていません" + }, + "auto_start_server": "アプリ起動時にサーバーを自動起動", + "auto_start_server.help": "有効にすると、アプリ起動時に音声認識サーバーが自動的に起動します", + "language": "認識言語" }, "voice": { - "title": "[to be translated]:语音功能", - "help": "[to be translated]:语音功能包括文本转语音(TTS)和语音识别(ASR)。", - "learn_more": "[to be translated]:了解更多" + "title": "音声機能", + "help": "音声機能にはテキスト読み上げ(TTS)と音声認識(ASR)が含まれます。", + "learn_more": "詳細を見る" }, "voice_call": { - "tab_title": "[to be translated]:通话功能", - "enable": "[to be translated]:启用语音通话", - "enable.help": "[to be translated]:启用后可以使用语音通话功能与AI进行对话", - "model": "[to be translated]:通话模型", - "model.select": "[to be translated]:选择模型", - "model.current": "[to be translated]:当前模型: {{model}}", - "model.info": "[to be translated]:选择用于语音通话的AI模型,不同模型可能有不同的语音交互体验", - "asr_tts_info": "[to be translated]:语音通话使用上面的语音识别(ASR)和语音合成(TTS)设置", - "test": "[to be translated]:测试通话", - "test_info": "[to be translated]:请使用输入框右侧的语音通话按钮进行测试" + "tab_title": "通話機能", + "enable": "音声通話を有効にする", + "enable.help": "有効にすると、音声通話機能を使用してAIと対話できます", + "model": "通話モデル", + "model.select": "モデルを選択", + "model.current": "現在のモデル: {{model}}", + "model.info": "音声通話用のAIモデルを選択します。モデルによって音声対話の体験が異なる場合があります", + "welcome_message": "こんにちは、AIアシスタントです。会話を始めるには、ボタンを長押ししてください。", + "prompt": { + "label": "音声通話プロンプト", + "placeholder": "音声通話プロンプトを入力", + "save": "保存", + "reset": "リセット", + "saved": "プロンプトが保存されました", + "reset_done": "プロンプトがリセットされました", + "info": "このプロンプトは音声通話モードでのAIの応答方法を指導します", + "language_info": "リセットボタンをクリックすると、現在の言語のデフォルトプロンプトが取得されます" + }, + "asr_tts_info": "音声通話は上記の音声認識(ASR)と音声合成(TTS)の設定を使用します", + "test": "音声通話テスト", + "test_info": "入力ボックスの右側にある音声通話ボタンを使用してテストしてください" } }, "translate": { @@ -1529,26 +1551,26 @@ "visualization": "可視化" }, "voice_call": { - "title": "[to be translated]:语音通话", - "start": "[to be translated]:开始语音通话", - "end": "[to be translated]:结束通话", - "mute": "[to be translated]:静音", - "unmute": "[to be translated]:取消静音", - "pause": "[to be translated]:暂停", - "resume": "[to be translated]:继续", - "you": "[to be translated]:您", - "ai": "[to be translated]:AI", - "press_to_talk": "[to be translated]:长按说话", - "release_to_send": "[to be translated]:松开发送", - "initialization_failed": "[to be translated]:初始化语音通话失败", - "error": "[to be translated]:语音通话出错", - "initializing": "[to be translated]:正在初始化语音通话...", - "ready": "[to be translated]:语音通话已就绪", - "shortcut_key_setting": "[to be translated]:语音识别快捷键设置", - "press_any_key": "[to be translated]:请按任意键...", - "save": "[to be translated]:保存", - "cancel": "[to be translated]:取消", - "shortcut_key_tip": "[to be translated]:按下此快捷键开始录音,松开快捷键结束录音并发送" + "title": "音声通話", + "start": "音声通話を開始", + "end": "通話を終了", + "mute": "ミュート", + "unmute": "ミュート解除", + "pause": "一時停止", + "resume": "再開", + "you": "あなた", + "ai": "AI", + "press_to_talk": "長押しして話す", + "release_to_send": "離すと送信", + "initialization_failed": "音声通話の初期化に失敗しました", + "error": "音声通話エラー", + "initializing": "音声通話を初期化中...", + "ready": "音声通話の準備が完了しました", + "shortcut_key_setting": "音声認識ショートカットキー設定", + "press_any_key": "任意のキーを押してください...", + "save": "保存", + "cancel": "キャンセル", + "shortcut_key_tip": "このショートカットキーを押すと録音が始まり、キーを離すと録音が終了して送信されます" } } } \ No newline at end of file diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 0833eb7f5c..156e46833b 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1421,7 +1421,15 @@ "mstts.voice": "[to be translated]:免费在线 TTS音色", "mstts.output_format": "[to be translated]:输出格式", "mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。", - "error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色" + "error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色", + "play": "[to be translated]:播放语音", + "stop": "[to be translated]:停止播放", + "speak": "[to be translated]:播放语音", + "stop_global": "[to be translated]:停止所有语音播放", + "stopped": "[to be translated]:已停止语音播放", + "segmented": "[to be translated]:分段", + "segmented_play": "[to be translated]:分段播放", + "segmented_playback": "[to be translated]:分段播放" }, "voice": { "title": "[to be translated]:语音功能", @@ -1474,7 +1482,9 @@ "browser_not_support": "[to be translated]:浏览器不支持语音识别", "start_failed": "[to be translated]:开始录音失败", "transcribe_failed": "[to be translated]:语音识别失败" - } + }, + "auto_start_server": "[to be translated]:启动应用自动开启服务器", + "auto_start_server.help": "[to be translated]:启用后,应用启动时会自动开启语音识别服务器" }, "voice_call": { "tab_title": "[to be translated]:通话功能", @@ -1484,9 +1494,19 @@ "model.select": "[to be translated]:选择模型", "model.current": "[to be translated]:当前模型: {{model}}", "model.info": "[to be translated]:选择用于语音通话的AI模型,不同模型可能有不同的语音交互体验", - "asr_tts_info": "[to be translated]:语音通话使用上面的语音识别(ASR)和语音合成(TTS)设置", - "test": "[to be translated]:测试通话", - "test_info": "[to be translated]:请使用输入框右侧的语音通话按钮进行测试" + "prompt": { + "label": "Подсказка для голосового вызова", + "placeholder": "Введите подсказку для голосового вызова", + "save": "Сохранить", + "reset": "Сбросить", + "saved": "Подсказка сохранена", + "reset_done": "Подсказка сброшена", + "info": "Эта подсказка будет направлять ответы ИИ в режиме голосового вызова", + "language_info": "Нажмите кнопку сброса, чтобы получить стандартную подсказку для текущего языка" + }, + "asr_tts_info": "Голосовой вызов использует настройки распознавания речи (ASR) и синтеза речи (TTS), указанные выше", + "test": "Тестировать голосовой вызов", + "test_info": "Используйте кнопку голосового вызова справа от поля ввода для тестирования" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 0aac3ddd40..396bed9daf 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1432,7 +1432,9 @@ "filter.markdown": "过滤Markdown标记", "filter.code_blocks": "过滤代码块", "filter.html_tags": "过滤HTML标签", + "filter.emojis": "过滤表情符号", "max_text_length": "最大文本长度", + "show_progress_bar": "显示TTS进度条", "test": "测试语音", "help": "语音合成功能支持将文本转换为自然语音。", "learn_more": "了解更多", @@ -1516,6 +1518,16 @@ "model.select": "选择模型", "model.current": "当前模型: {{model}}", "model.info": "选择用于语音通话的AI模型,不同模型可能有不同的语音交互体验", + "welcome_message": "您好,我是您的AI助手,请长按说话按钮进行对话。", + "prompt": { + "label": "语音通话提示词", + "placeholder": "请输入语音通话提示词", + "save": "保存", + "reset": "重置", + "saved": "提示词已保存", + "reset_done": "提示词已重置", + "info": "此提示词将指导AI在语音通话模式下的回复方式" + }, "asr_tts_info": "语音通话使用上面的语音识别(ASR)和语音合成(TTS)设置", "test": "测试通话", "test_info": "请使用输入框右侧的语音通话按钮进行测试" diff --git a/src/renderer/src/i18n/locales/zh-cn.json.bak b/src/renderer/src/i18n/locales/zh-cn.json.bak new file mode 100644 index 0000000000..396bed9daf --- /dev/null +++ b/src/renderer/src/i18n/locales/zh-cn.json.bak @@ -0,0 +1,1576 @@ +{ + "translation": { + "voice_call": { + "title": "语音通话", + "start": "开始语音通话", + "end": "结束通话", + "mute": "静音", + "unmute": "取消静音", + "pause": "暂停", + "resume": "继续", + "you": "您", + "ai": "AI", + "press_to_talk": "长按说话", + "release_to_send": "松开发送", + "initialization_failed": "初始化语音通话失败", + "error": "语音通话出错", + "initializing": "正在初始化语音通话...", + "ready": "语音通话已就绪", + "shortcut_key_setting": "语音识别快捷键设置", + "press_any_key": "请按任意键...", + "save": "保存", + "cancel": "取消", + "shortcut_key_tip": "按下此快捷键开始录音,松开快捷键结束录音并发送" + }, + "agents": { + "add.button": "添加到助手", + "add.knowledge_base": "知识库", + "add.knowledge_base.placeholder": "选择知识库", + "add.name": "名称", + "add.name.placeholder": "输入名称", + "add.prompt": "提示词", + "add.prompt.placeholder": "输入提示词", + "add.title": "创建智能体", + "delete.popup.content": "确定要删除此智能体吗?", + "edit.message.add.title": "添加", + "edit.message.assistant.placeholder": "输入助手消息", + "edit.message.assistant.title": "助手", + "edit.message.empty.content": "会话输入内容不能为空", + "edit.message.group.title": "消息组", + "edit.message.title": "预设消息", + "edit.message.user.placeholder": "输入用户消息", + "edit.message.user.title": "用户", + "edit.model.select.title": "选择模型", + "edit.settings.hide_preset_messages": "隐藏预设消息", + "edit.title": "编辑智能体", + "manage.title": "管理智能体", + "my_agents": "我的智能体", + "search.no_results": "没有找到相关智能体", + "sorting.title": "排序", + "tag.agent": "智能体", + "tag.default": "默认", + "tag.new": "新建", + "tag.system": "系统", + "title": "智能体" + }, + "assistants": { + "title": "助手", + "abbr": "助手", + "settings.title": "助手设置", + "clear.content": "清空话题会删除助手下所有话题和文件,确定要继续吗?", + "clear.title": "清空话题", + "copy.title": "复制助手", + "delete.content": "删除助手会删除所有该助手下的话题和文件,确定要继续吗?", + "delete.title": "删除助手", + "edit.title": "编辑助手", + "save.success": "保存成功", + "save.title": "保存到智能体", + "search": "搜索助手", + "settings.mcp": "MCP 服务器", + "settings.mcp.enableFirst": "请先在 MCP 设置中启用此服务器", + "settings.mcp.title": "MCP 设置", + "settings.mcp.noServersAvailable": "无可用 MCP 服务器。请在设置中添加服务器", + "settings.mcp.description": "默认启用的 MCP 服务器", + "settings.default_model": "默认模型", + "settings.knowledge_base": "知识库设置", + "settings.model": "模型设置", + "settings.preset_messages": "预设消息", + "settings.prompt": "提示词设置", + "settings.reasoning_effort": "思维链长度", + "settings.reasoning_effort.high": "长", + "settings.reasoning_effort.low": "短", + "settings.reasoning_effort.medium": "中", + "settings.reasoning_effort.off": "关", + "settings.reasoning_effort.tip": "仅支持 OpenAI o-series、Anthropic、Grok 推理模型", + "settings.more": "助手设置" + }, + "auth": { + "error": "自动获取密钥失败,请手动获取", + "get_key": "获取", + "get_key_success": "自动获取密钥成功", + "login": "登录", + "oauth_button": "使用{{provider}}登录" + }, + "backup": { + "confirm": "确定要备份数据吗?", + "confirm.button": "选择备份位置", + "content": "备份全部数据,包括聊天记录、设置、知识库等所有数据。请注意,备份过程可能需要一些时间,感谢您的耐心等待。", + "progress": { + "completed": "备份完成", + "compressing": "压缩文件...", + "copying_files": "复制文件... {{progress}}%", + "preparing": "准备备份...", + "title": "备份进度", + "writing_data": "写入数据..." + }, + "title": "数据备份" + }, + "button": { + "add": "添加", + "added": "已添加", + "collapse": "收起", + "manage": "管理", + "select_model": "选择模型", + "show.all": "显示全部", + "update_available": "有可用更新" + }, + "chat": { + "add.assistant.title": "添加助手", + "artifacts.button.download": "下载", + "artifacts.button.openExternal": "外部浏览器打开", + "artifacts.button.preview": "预览", + "artifacts.preview.openExternal.error.content": "外部浏览器打开出错", + "assistant.search.placeholder": "搜索", + "deeply_thought": "已深度思考(用时 {{secounds}} 秒)", + "default.description": "你好,我是默认助手。你可以立刻开始跟我聊天。", + "default.name": "默认助手", + "default.topic.name": "默认话题", + "tts": { + "play": "播放语音", + "stop": "停止播放", + "speak": "播放语音", + "stop_global": "停止所有语音播放", + "stopped": "已停止语音播放" + }, + "history": { + "assistant_node": "助手", + "click_to_navigate": "点击跳转到对应消息", + "coming_soon": "聊天工作流图表即将上线", + "no_messages": "没有找到消息", + "start_conversation": "开始对话以查看聊天流程图", + "title": "聊天历史", + "user_node": "用户", + "view_full_content": "查看完整内容" + }, + "input.auto_resize": "自动调整高度", + "input.clear": "清空消息 {{Command}}", + "input.clear.content": "确定要清除当前会话所有消息吗?", + "input.clear.title": "清空消息", + "input.collapse": "收起", + "input.context_count.tip": "上下文数 / 最大上下文数", + "input.estimated_tokens.tip": "预估 token 数", + "input.expand": "展开", + "input.file_not_supported": "模型不支持此文件类型", + "input.generate_image": "生成图片", + "input.generate_image_not_supported": "模型不支持生成图片", + "input.knowledge_base": "知识库", + "input.new.context": "清除上下文 {{Command}}", + "input.new_topic": "新话题 {{Command}}", + "input.pause": "暂停", + "input.placeholder": "在这里输入消息...", + "input.translating": "翻译中...", + "input.send": "发送", + "input.settings": "设置", + "input.topics": " 话题 ", + "input.translate": "翻译成{{target_language}}", + "input.upload": "上传图片或文档", + "input.upload.upload_from_local": "上传本地文件...", + "input.upload.document": "上传文档(模型不支持图片)", + "input.web_search": "开启网络搜索", + "input.web_search.button.ok": "去设置", + "input.web_search.enable": "开启网络搜索", + "input.web_search.enable_content": "需要先在设置中检查网络搜索连通性", + "message.new.branch": "分支", + "message.new.branch.created": "新分支已创建", + "message.new.context": "清除上下文", + "message.quote": "引用", + "message.regenerate.model": "切换模型", + "message.useful": "有用", + "navigation": { + "first": "已经是第一条消息", + "history": "聊天历史", + "last": "已经是最后一条消息", + "next": "下一条消息", + "prev": "上一条消息", + "top": "回到顶部", + "bottom": "回到底部", + "close": "关闭" + }, + "resend": "重新发送", + "save": "保存", + "settings.code_collapsible": "代码块可折叠", + "settings.code_wrappable": "代码块可换行", + "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": "允许缓存的最小代码长度(千字符),超过阈值的代码块才会被缓存", + "settings.context_count": "上下文数", + "settings.context_count.tip": "要保留在上下文中的消息数量,数值越大,上下文越长,消耗的 token 越多。普通聊天建议 5-10", + "settings.max": "不限", + "settings.max_tokens": "开启消息长度限制", + "settings.max_tokens.confirm": "开启消息长度限制", + "settings.max_tokens.confirm_content": "开启消息长度限制后,单次交互所用的最大 Token 数, 会影响返回结果的长度。要根据模型上下文限制来设置,否则会报错", + "settings.max_tokens.tip": "单次交互所用的最大 Token 数, 会影响返回结果的长度。要根据模型上下文限制来设置,否则会报错", + "settings.reset": "重置", + "settings.set_as_default": "应用到默认助手", + "settings.show_line_numbers": "代码显示行号", + "settings.temperature": "模型温度", + "settings.temperature.tip": "模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7", + "settings.thought_auto_collapse": "思考内容自动折叠", + "settings.thought_auto_collapse.tip": "思考结束后思考内容自动折叠", + "settings.top_p": "Top-P", + "settings.top_p.tip": "默认值为 1,值越小,AI 生成的内容越单调,也越容易理解;值越大,AI 回复的词汇围越大,越多样化", + "suggestions.title": "建议的问题", + "thinking": "思考中", + "topics.auto_rename": "生成话题名", + "topics.clear.title": "清空消息", + "topics.copy.image": "复制为图片", + "topics.copy.md": "复制为 Markdown", + "topics.copy.title": "复制", + "topics.delete.shortcut": "按住 {{key}} 可直接删除", + "topics.edit.placeholder": "输入新名称", + "topics.edit.title": "编辑话题名", + "topics.export.image": "导出为图片", + "topics.export.joplin": "导出到 Joplin", + "topics.export.md": "导出为 Markdown", + "topics.export.md.reason": "导出为 Markdown (包含思考)", + "topics.export.notion": "导出到 Notion", + "topics.export.obsidian": "导出到 Obsidian", + "topics.export.obsidian_vault": "保管库", + "topics.export.obsidian_vault_placeholder": "请选择保管库名称", + "topics.export.obsidian_path": "路径", + "topics.export.obsidian_path_placeholder": "请选择路径", + "topics.export.obsidian_atributes": "配置笔记属性", + "topics.export.obsidian_btn": "确定", + "topics.export.obsidian_created": "创建时间", + "topics.export.obsidian_created_placeholder": "请选择创建时间", + "topics.export.obsidian_export_failed": "导出到Obsidian失败", + "topics.export.obsidian_export_success": "导出到Obsidian成功", + "topics.export.obsidian_operate": "处理方式", + "topics.export.obsidian_operate_append": "追加", + "topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆盖)", + "topics.export.obsidian_operate_placeholder": "请选择处理方式", + "topics.export.obsidian_operate_prepend": "前置", + "topics.export.obsidian_source": "来源", + "topics.export.obsidian_source_placeholder": "请输入来源", + "topics.export.obsidian_tags": "标签", + "topics.export.obsidian_tags_placeholder": "请输入标签,多个标签用英文逗号分隔", + "topics.export.obsidian_title": "标题", + "topics.export.obsidian_title_placeholder": "请输入标题", + "topics.export.obsidian_title_required": "标题不能为空", + "topics.export.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.title": "导出", + "topics.export.word": "导出为 Word", + "topics.export.yuque": "导出到语雀", + "topics.list": "话题列表", + "topics.move_to": "移动到", + "topics.new": "开始新对话", + "topics.pinned": "固定话题", + "topics.prompt": "话题提示词", + "topics.prompt.edit.title": "编辑话题提示词", + "topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词", + "topics.title": "话题", + "topics.unpinned": "取消固定", + "translate": "翻译", + "topics.export.siyuan": "导出到思源笔记", + "topics.export.wait_for_title_naming": "正在生成标题...", + "topics.export.title_naming_success": "标题生成成功", + "topics.export.title_naming_failed": "标题生成失败,使用默认标题" + }, + "code_block": { + "collapse": "收起", + "disable_wrap": "取消换行", + "enable_wrap": "换行", + "expand": "展开" + }, + "common": { + "add": "添加", + "advanced_settings": "高级设置", + "and": "和", + "assistant": "智能体", + "avatar": "头像", + "back": "返回", + "cancel": "取消", + "chat": "聊天", + "clear": "清除", + "close": "关闭", + "confirm": "确认", + "copied": "已复制", + "copy": "复制", + "cut": "剪切", + "default": "默认", + "delete": "删除", + "description": "描述", + "docs": "文档", + "download": "下载", + "duplicate": "复制", + "edit": "编辑", + "expand": "展开", + "collapse": "折叠", + "footnote": "引用内容", + "footnotes": "引用内容", + "fullscreen": "已进入全屏模式,按 F11 退出", + "knowledge_base": "知识库", + "language": "语言", + "model": "模型", + "models": "模型", + "more": "更多", + "name": "名称", + "paste": "粘贴", + "prompt": "提示词", + "provider": "提供商", + "regenerate": "重新生成", + "rename": "重命名", + "reset": "重置", + "save": "保存", + "search": "搜索", + "select": "选择", + "topics": "话题", + "warning": "警告", + "you": "用户", + "reasoning_content": "已深度思考", + "sort": { + "pinyin": "按拼音排序", + "pinyin.asc": "按拼音升序", + "pinyin.desc": "按拼音降序" + } + }, + "docs": { + "title": "帮助文档" + }, + "error": { + "backup.file_format": "备份文件格式错误", + "chat.response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥", + "http": { + "400": "请求错误,请检查请求参数是否正确。如果修改了模型设置,请重置到默认设置", + "401": "身份验证失败,请检查 API 密钥是否正确", + "403": "禁止访问,请翻译具体报错信息查看原因,或联系服务商询问被禁止原因", + "404": "模型不存在或者请求路径错误", + "429": "请求速率超过限制,请稍后再试", + "500": "服务器错误,请稍后再试", + "502": "网关错误,请稍后再试", + "503": "服务不可用,请稍后再试", + "504": "网关超时,请稍后再试" + }, + "model.exists": "模型已存在", + "no_api_key": "API 密钥未配置", + "provider_disabled": "模型提供商未启用", + "render": { + "description": "渲染公式失败,请检查公式格式是否正确", + "title": "渲染错误" + }, + "user_message_not_found": "无法找到原始用户消息", + "unknown": "未知错误" + }, + "export": { + "assistant": "助手", + "attached_files": "附件", + "conversation_details": "会话详情", + "conversation_history": "会话历史", + "created": "创建时间", + "last_updated": "最后更新", + "messages": "消息数", + "user": "用户" + }, + "files": { + "actions": "操作", + "all": "所有文件", + "count": "个文件", + "created_at": "创建时间", + "delete": "删除", + "delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?", + "delete.paintings.warning": "绘图中包含该图片,暂时无法删除", + "delete.title": "删除文件", + "document": "文档", + "edit": "编辑", + "file": "文件", + "image": "图片", + "name": "文件名", + "open": "打开", + "size": "大小", + "text": "文本", + "title": "文件", + "type": "类型" + }, + "gpustack": { + "keep_alive_time.description": "模型在内存中保持的时间(默认:5分钟)", + "keep_alive_time.placeholder": "分钟", + "keep_alive_time.title": "保持活跃时间", + "title": "GPUStack" + }, + "history": { + "continue_chat": "继续聊天", + "locate.message": "定位到消息", + "search.messages": "搜索所有消息", + "search.placeholder": "搜索话题或消息...", + "search.topics.empty": "没有找到相关话题, 点击回车键搜索所有消息", + "title": "话题搜索" + }, + "knowledge": { + "add": { + "title": "添加知识库" + }, + "add_directory": "添加目录", + "add_file": "添加文件", + "add_note": "添加笔记", + "add_sitemap": "站点地图", + "add_url": "添加网址", + "cancel_index": "取消索引", + "chunk_overlap": "重叠大小", + "chunk_overlap_placeholder": "默认值(不建议修改)", + "chunk_overlap_tooltip": "相邻文本块之间重复的内容量,确保分段后的文本块之间仍然有上下文联系,提升模型处理长文本的整体效果", + "chunk_size": "分段大小", + "chunk_size_change_warning": "分段大小和重叠大小修改只针对新添加的内容有效", + "chunk_size_placeholder": "默认值(不建议修改)", + "chunk_size_too_large": "分段大小不能超过模型上下文限制({{max_context}})", + "chunk_size_tooltip": "将文档切割分段,每段的大小,不能超过模型上下文限制", + "clear_selection": "清除选择", + "delete": "删除", + "delete_confirm": "确定要删除此知识库吗?", + "directories": "目录", + "directory_placeholder": "请输入目录路径", + "document_count": "请求文档片段数量", + "document_count_default": "默认", + "document_count_help": "请求文档片段数量越多,附带的信息越多,但需要消耗的 Token 也越多", + "drag_file": "拖拽文件到这里", + "edit_remark": "修改备注", + "edit_remark_placeholder": "请输入备注内容", + "empty": "暂无知识库", + "file_hint": "支持 {{file_types}} 格式", + "index_all": "索引全部", + "index_cancelled": "索引已取消", + "index_started": "索引开始", + "invalid_url": "无效的网址", + "model_info": "模型信息", + "no_bases": "暂无知识库", + "no_match": "未匹配到知识库内容", + "no_provider": "知识库模型服务商丢失,该知识库将不再支持,请重新创建知识库", + "not_set": "未设置", + "not_support": "知识库数据库引擎已更新,该知识库将不再支持,请重新创建知识库", + "notes": "笔记", + "notes_placeholder": "输入此知识库的附加信息或上下文...", + "rename": "重命名", + "search": "搜索知识库", + "search_placeholder": "输入查询内容", + "settings": "知识库设置", + "sitemap_placeholder": "请输入站点地图 URL", + "sitemaps": "网站", + "source": "来源", + "status": "状态", + "status_completed": "已完成", + "status_failed": "失败", + "status_new": "已添加", + "status_pending": "等待中", + "status_processing": "处理中", + "threshold": "匹配度阈值", + "threshold_placeholder": "未设置", + "threshold_too_large_or_small": "阈值不能大于1或小于0", + "threshold_tooltip": "用于衡量用户问题与知识库内容之间的相关性(0-1)", + "title": "知识库", + "topN": "返回结果数量", + "topN__too_large_or_small": "返回结果数量不能大于100或小于1", + "topN_placeholder": "未设置", + "topN_tooltip": "返回的匹配结果数量,数值越大,匹配结果越多,但消耗的 Token 也越多", + "url_added": "网址已添加", + "url_placeholder": "请输入网址, 多个网址用回车分隔", + "urls": "网址" + }, + "languages": { + "arabic": "阿拉伯文", + "chinese": "简体中文", + "chinese-traditional": "繁体中文", + "english": "英文", + "french": "法文", + "german": "德文", + "italian": "意大利文", + "japanese": "日文", + "korean": "韩文", + "portuguese": "葡萄牙文", + "russian": "俄文", + "spanish": "西班牙文" + }, + "lmstudio": { + "keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)", + "keep_alive_time.placeholder": "分钟", + "keep_alive_time.title": "保持活跃时间", + "title": "LM Studio" + }, + "mermaid": { + "download": { + "png": "下载 PNG", + "svg": "下载 SVG" + }, + "resize": { + "zoom-in": "放大", + "zoom-out": "缩小" + }, + "tabs": { + "preview": "预览", + "source": "源码" + }, + "title": "Mermaid 图表" + }, + "message": { + "api.check.model.title": "请选择要检测的模型", + "api.connection.failed": "连接失败", + "api.connection.success": "连接成功", + "assistant.added.content": "智能体添加成功", + "attachments": { + "pasted_image": "剪切板图片", + "pasted_text": "剪切板文件" + }, + "backup.failed": "备份失败", + "backup.start.success": "开始备份", + "backup.success": "备份成功", + "chat.completion.paused": "会话已停止", + "citations": "引用内容", + "copied": "已复制", + "copy.failed": "复制失败", + "copy.success": "复制成功", + "error.chunk_overlap_too_large": "分段重叠不能大于分段大小", + "error.dimension_too_large": "内容尺寸过大", + "error.enter.api.host": "请输入您的 API 地址", + "error.enter.api.key": "请输入您的 API 密钥", + "error.enter.model": "请选择一个模型", + "error.enter.name": "请输入知识库名称", + "error.get_embedding_dimensions": "获取嵌入维度失败", + "error.invalid.api.host": "无效的 API 地址", + "error.invalid.api.key": "无效的 API 密钥", + "error.invalid.enter.model": "请选择一个模型", + "error.invalid.proxy.url": "无效的代理地址", + "error.invalid.webdav": "无效的 WebDAV 设置", + "error.joplin.export": "导出 Joplin 失败,请保持 Joplin 已运行并检查连接状态或检查配置", + "error.joplin.no_config": "未配置 Joplin 授权令牌 或 URL", + "error.invalid.nutstore": "无效的坚果云设置", + "error.invalid.nutstore_token": "无效的坚果云 Token", + "error.markdown.export.preconf": "导出Markdown文件到预先设定的路径失败", + "error.markdown.export.specified": "导出Markdown文件失败", + "error.notion.export": "导出 Notion 错误,请检查连接状态并对照文档检查配置", + "error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID", + "error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置", + "error.yuque.no_config": "未配置语雀 Token 或 知识库 URL", + "group.delete.content": "删除分组消息会删除用户提问和所有助手的回答", + "group.delete.title": "删除分组消息", + "ignore.knowledge.base": "联网模式开启,忽略知识库", + "info.notion.block_reach_limit": "对话过长,正在分页导出到Notion", + "loading.notion.exporting_progress": "正在导出到Notion ({{current}}/{{total}})...", + "loading.notion.preparing": "正在准备导出到Notion...", + "mention.title": "切换模型回答", + "message.code_style": "代码风格", + "message.delete.content": "确定要删除此消息吗?", + "message.delete.title": "删除消息", + "message.multi_model_style": "多模型回答样式", + "message.multi_model_style.fold": "标签模式", + "message.multi_model_style.fold.compress": "切换到紧凑排列", + "message.multi_model_style.fold.expand": "切换到展开排列", + "message.multi_model_style.grid": "卡片布局", + "message.multi_model_style.horizontal": "横向排列", + "message.multi_model_style.vertical": "纵向堆叠", + "message.style": "消息样式", + "message.style.bubble": "气泡", + "message.style.plain": "简洁", + "regenerate.confirm": "重新生成会覆盖当前消息", + "reset.confirm.content": "确定要重置所有数据吗?", + "reset.double.confirm.content": "你的全部数据都会丢失,如果没有备份数据,将无法恢复,确定要继续吗?", + "reset.double.confirm.title": "数据丢失!!!", + "restore.failed": "恢复失败", + "restore.success": "恢复成功", + "save.success.title": "保存成功", + "searching": "正在联网搜索...", + "success.joplin.export": "成功导出到 Joplin", + "success.markdown.export.preconf": "成功导出 Markdown 文件到预先设定的路径", + "success.markdown.export.specified": "成功导出 Markdown 文件", + "success.notion.export": "成功导出到 Notion", + "success.yuque.export": "成功导出到语雀", + "switch.disabled": "请等待当前回复完成后操作", + "tools": { + "completed": "已完成", + "invoking": "调用中" + }, + "topic.added": "话题添加成功", + "upgrade.success.button": "重启", + "upgrade.success.content": "重启用以完成升级", + "upgrade.success.title": "升级成功", + "warn.notion.exporting": "正在导出到 Notion, 请勿重复请求导出!", + "warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试", + "error.siyuan.export": "导出思源笔记失败,请检查连接状态并对照文档检查配置", + "error.siyuan.no_config": "未配置思源笔记API地址或令牌", + "success.siyuan.export": "导出到思源笔记成功", + "warn.yuque.exporting": "正在导出语雀, 请勿重复请求导出!", + "warn.siyuan.exporting": "正在导出到思源笔记,请勿重复请求导出!" + }, + "minapp": { + "popup": { + "refresh": "刷新", + "close": "关闭小程序", + "minimize": "最小化小程序", + "devtools": "开发者工具", + "openExternal": "在浏览器中打开", + "rightclick_copyurl": "右键复制URL" + }, + "sidebar.add.title": "添加到侧边栏", + "sidebar.remove.title": "从侧边栏移除", + "sidebar.close.title": "关闭", + "sidebar.closeall.title": "全部关闭", + "sidebar.hide.title": "隐藏小程序", + "title": "小程序" + }, + "miniwindow": { + "clipboard": { + "empty": "剪贴板为空" + }, + "feature": { + "chat": "回答此问题", + "explanation": "解释说明", + "summary": "内容总结", + "translate": "文本翻译" + }, + "footer": { + "copy_last_message": "按 C 键复制", + "backspace_clear": "按 Backspace 清空", + "esc": "按 ESC {{action}}", + "esc_back": "返回", + "esc_close": "关闭" + }, + "input": { + "placeholder": { + "empty": "询问 {{model}} 获取帮助...", + "title": "你想对下方文字做什么" + } + }, + "tooltip": { + "pin": "窗口置顶" + } + }, + "models": { + "add_parameter": "添加参数", + "all": "全部", + "custom_parameters": "自定义参数", + "dimensions": "{{dimensions}} 维", + "edit": "编辑模型", + "embedding": "嵌入", + "embedding_model": "嵌入模型", + "embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加", + "function_calling": "函数调用", + "no_matches": "无可用模型", + "parameter_name": "参数名称", + "parameter_type": { + "boolean": "布尔值", + "json": "JSON", + "number": "数字", + "string": "文本" + }, + "pinned": "已固定", + "rerank_model": "重排模型", + "rerank_model_support_provider": "目前重排序模型仅支持部分服务商 ({{provider}})", + "rerank_model_tooltip": "在设置->模型服务中点击管理按钮添加", + "search": "搜索模型...", + "stream_output": "流式输出", + "type": { + "embedding": "嵌入", + "free": "免费", + "function_calling": "工具", + "reasoning": "推理", + "rerank": "重排", + "select": "选择模型类型", + "text": "文本", + "vision": "视觉", + "websearch": "联网" + } + }, + "navbar": { + "expand": "伸缩对话框", + "hide_sidebar": "隐藏侧边栏", + "show_sidebar": "显示侧边栏" + }, + "ollama": { + "keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)", + "keep_alive_time.placeholder": "分钟", + "keep_alive_time.title": "保持活跃时间", + "title": "Ollama" + }, + "paintings": { + "button.delete.image": "删除图片", + "button.delete.image.confirm": "确定要删除此图片吗?", + "button.new.image": "新建图片", + "guidance_scale": "引导比例", + "guidance_scale_tip": "无分类器指导。控制模型在寻找相关图像时对提示词的遵循程度", + "image.size": "图片尺寸", + "inference_steps": "推理步数", + "inference_steps_tip": "要执行的推理步数。步数越多,质量越高但耗时越长", + "negative_prompt": "反向提示词", + "negative_prompt_tip": "描述你不想在图片中出现的内容", + "number_images": "生成数量", + "number_images_tip": "一次生成的图片数量 (1-4)", + "prompt_enhancement": "提示词增强", + "prompt_enhancement_tip": "开启后将提示重写为详细的、适合模型的版本", + "prompt_placeholder": "描述你想创建的图片,例如:一个宁静的湖泊,夕阳西下,远处是群山", + "regenerate.confirm": "这将覆盖已生成的图片,是否继续?", + "seed": "随机种子", + "seed_tip": "相同的种子和提示词可以生成相似的图片", + "title": "图片" + }, + "plantuml": { + "download": { + "failed": "下载失败,请检查网络", + "png": "下载 PNG", + "svg": "下载 SVG" + }, + "tabs": { + "preview": "预览", + "source": "源码" + }, + "title": "PlantUML 图表" + }, + "prompts": { + "explanation": "帮我解释一下这个概念", + "summarize": "帮我总结一下这段话", + "title": "你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号" + }, + "provider": { + "aihubmix": "AiHubMix", + "alayanew": "Alaya NeW", + "anthropic": "Anthropic", + "azure-openai": "Azure OpenAI", + "baichuan": "百川", + "baidu-cloud": "百度云千帆", + "copilot": "GitHub Copilot", + "dashscope": "阿里云百炼", + "deepseek": "深度求索", + "dmxapi": "DMXAPI", + "doubao": "火山引擎", + "fireworks": "Fireworks", + "gemini": "Gemini", + "gitee-ai": "Gitee AI", + "github": "GitHub Models", + "gpustack": "GPUStack", + "grok": "Grok", + "groq": "Groq", + "hunyuan": "腾讯混元", + "hyperbolic": "Hyperbolic", + "infini": "无问芯穹", + "jina": "Jina", + "lmstudio": "LM Studio", + "minimax": "MiniMax", + "mistral": "Mistral", + "modelscope": "ModelScope 魔搭", + "moonshot": "月之暗面", + "nvidia": "英伟达", + "o3": "O3", + "ocoolai": "ocoolAI", + "ollama": "Ollama", + "openai": "OpenAI", + "openrouter": "OpenRouter", + "perplexity": "Perplexity", + "ppio": "PPIO 派欧云", + "qwenlm": "QwenLM", + "silicon": "硅基流动", + "stepfun": "阶跃星辰", + "tencent-cloud-ti": "腾讯云TI", + "together": "Together", + "xirang": "天翼云息壤", + "yi": "零一万物", + "zhinao": "360智脑", + "zhipu": "智谱AI", + "voyageai": "Voyage AI", + "qiniu": "七牛云" + }, + "restore": { + "confirm": "确定要恢复数据吗?", + "confirm.button": "选择备份文件", + "content": "恢复操作将使用备份数据覆盖当前所有应用数据。请注意,恢复过程可能需要一些时间,感谢您的耐心等待。", + "progress": { + "completed": "恢复完成", + "copying_files": "复制文件... {{progress}}%", + "extracting": "解压备份...", + "preparing": "准备恢复...", + "reading_data": "读取数据...", + "title": "恢复进度" + }, + "title": "数据恢复" + }, + "settings": { + "about": "关于我们", + "about.checkingUpdate": "正在检查更新...", + "about.checkUpdate": "检查更新", + "about.checkUpdate.available": "立即更新", + "about.contact.button": "邮件", + "about.contact.title": "邮件联系", + "about.description": "一款为创造者而生的 AI 助手", + "about.downloading": "正在下载更新...", + "about.feedback.button": "反馈", + "about.feedback.title": "意见反馈", + "about.license.button": "查看", + "about.license.title": "许可证", + "about.releases.button": "查看", + "about.releases.title": "更新日志", + "about.social.title": "社交账号", + "about.title": "关于我们", + "about.updateAvailable": "发现新版本 {{version}}", + "about.updateError": "更新出错", + "about.updateNotAvailable": "你的软件已是最新版本", + "about.website.button": "查看", + "about.website.title": "官方网站", + "advanced.auto_switch_to_topics": "自动切换到话题", + "advanced.title": "高级设置", + "assistant": "默认助手", + "assistant.model_params": "模型参数", + "assistant.show.icon": "显示模型图标", + "assistant.title": "默认助手", + "data": { + "app_data": "应用数据", + "app_knowledge": "知识库文件", + "app_knowledge.button.delete": "删除文件", + "app_knowledge.remove_all": "删除知识库文件", + "app_knowledge.remove_all_confirm": "删除知识库文件可以减少存储空间占用,但不会删除知识库向量化数据,删除之后将无法打开源文件,是否删除?", + "app_knowledge.remove_all_success": "文件删除成功", + "app_logs": "应用日志", + "clear_cache": { + "button": "清除缓存", + "confirm": "清除缓存将删除应用缓存的数据,包括小程序数据。此操作不可恢复,是否继续?", + "error": "清除缓存失败", + "success": "缓存清除成功", + "title": "清除缓存" + }, + "data.title": "数据目录", + "divider.basic": "基础数据设置", + "divider.cloud_storage": "云备份设置", + "divider.export_settings": "导出设置", + "divider.third_party": "第三方连接", + "hour_interval_one": "{{count}} 小时", + "hour_interval_other": "{{count}} 小时", + "export_menu": { + "title": "导出菜单设置", + "image": "导出为图片", + "markdown": "导出为Markdown", + "markdown_reason": "导出为Markdown(包含思考)", + "notion": "导出到Notion", + "yuque": "导出到语雀", + "obsidian": "导出到Obsidian", + "siyuan": "导出到思源笔记", + "joplin": "导出到Joplin", + "docx": "导出为Word" + }, + "joplin": { + "check": { + "button": "检查", + "empty_token": "请先输入 Joplin 授权令牌", + "empty_url": "请先输入 Joplin 剪裁服务监听 URL", + "fail": "Joplin 连接验证失败", + "success": "Joplin 连接验证成功" + }, + "help": "在 Joplin 选项中,启用网页剪裁服务(无需安装浏览器插件),确认端口号,并复制授权令牌", + "title": "Joplin 配置", + "token": "Joplin 授权令牌", + "token_placeholder": "请输入 Joplin 授权令牌", + "url": "Joplin 剪裁服务监听 URL", + "url_placeholder": "http://127.0.0.1:41184/" + }, + "markdown_export.force_dollar_math.help": "开启后,导出Markdown时会将强制使用$$来标记LaTeX公式。注意:该项也会影响所有通过Markdown导出的方式,如Notion、语雀等。", + "markdown_export.force_dollar_math.title": "强制使用$$来标记LaTeX公式", + "markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框", + "markdown_export.path": "默认导出路径", + "markdown_export.path_placeholder": "导出路径", + "markdown_export.select": "选择", + "markdown_export.title": "Markdown 导出", + "message_title.use_topic_naming.title": "使用话题命名模型为导出的消息创建标题", + "message_title.use_topic_naming.help": "开启后,使用话题命名模型为导出的消息创建标题。该项也会影响所有通过Markdown导出的方式。", + "minute_interval_one": "{{count}} 分钟", + "minute_interval_other": "{{count}} 分钟", + "notion.api_key": "Notion 密钥", + "notion.api_key_placeholder": "请输入Notion 密钥", + "notion.auto_split": "导出对话时自动分页", + "notion.auto_split_tip": "当要导出的话题过长时自动分页导出到Notion", + "notion.check": { + "button": "检查", + "empty_api_key": "未配置 API key", + "empty_database_id": "未配置 Database ID", + "error": "连接异常,请检查网络及 API key 和 Database ID 是否正确", + "fail": "连接失败,请检查网络及 API key 和 Database ID 是否正确", + "success": "连接成功" + }, + "notion.database_id": "Notion 数据库 ID", + "notion.database_id_placeholder": "请输入Notion 数据库 ID", + "notion.help": "Notion 配置文档", + "notion.page_name_key": "页面标题字段名", + "notion.page_name_key_placeholder": "请输入页面标题字段名,默认为 Name", + "notion.split_size": "自动分页大小", + "notion.split_size_help": "Notion免费版用户建议设置为90,高级版用户建议设置为24990,默认值为90", + "notion.split_size_placeholder": "请输入每页块数限制(默认90)", + "notion.title": "Notion 配置", + "title": "数据设置", + "webdav": { + "autoSync": "自动备份", + "autoSync.off": "关闭", + "backup.button": "备份到 WebDAV", + "backup.modal.filename.placeholder": "请输入备份文件名", + "backup.modal.title": "备份到 WebDAV", + "host": "WebDAV 地址", + "host.placeholder": "http://localhost:8080", + "hour_interval_one": "{{count}} 小时", + "hour_interval_other": "{{count}} 小时", + "lastSync": "上次备份时间", + "minute_interval_one": "{{count}} 分钟", + "minute_interval_other": "{{count}} 分钟", + "noSync": "等待下次备份", + "password": "WebDAV 密码", + "path": "WebDAV 路径", + "path.placeholder": "/backup", + "restore.button": "从 WebDAV 恢复", + "restore.confirm.content": "从 WebDAV 恢复将会覆盖当前数据,是否继续?", + "restore.confirm.title": "确认恢复", + "restore.content": "从 WebDAV 恢复将覆盖当前数据,是否继续?", + "restore.modal.select.placeholder": "请选择要恢复的备份文件", + "restore.modal.title": "从 WebDAV 恢复", + "restore.title": "从 WebDAV 恢复", + "syncError": "备份错误", + "syncStatus": "备份状态", + "title": "WebDAV", + "user": "WebDAV 用户名" + }, + "yuque": { + "check": { + "button": "检查", + "empty_repo_url": "请先输入知识库URL", + "empty_token": "请先输入语雀Token", + "fail": "语雀连接验证失败", + "success": "语雀连接验证成功" + }, + "help": "获取语雀 Token", + "repo_url": "知识库 URL", + "repo_url_placeholder": "https://www.yuque.com/username/xxx", + "title": "语雀配置", + "token": "语雀 Token", + "token_placeholder": "请输入语雀Token" + }, + "obsidian": { + "title": "Obsidian 配置", + "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": "导出失败" + }, + "siyuan": { + "title": "思源笔记配置", + "api_url": "API地址", + "api_url_placeholder": "例如:http://127.0.0.1:6806", + "token": "API令牌", + "token.help": "在思源笔记->设置->关于中获取", + "token_placeholder": "请输入思源笔记令牌", + "box_id": "笔记本ID", + "box_id_placeholder": "请输入笔记本ID", + "root_path": "文档根路径", + "root_path_placeholder": "例如:/CherryStudio", + "check": { + "title": "连接检查", + "button": "检查", + "empty_config": "请填写API地址和令牌", + "success": "连接成功", + "fail": "连接失败,请检查API地址和令牌", + "error": "连接异常,请检查网络连接" + } + }, + "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": "新建文件夹" + } + }, + "display.assistant.title": "助手设置", + "display.custom.css": "自定义 CSS", + "display.custom.css.cherrycss": "从 cherrycss.com 获取", + "display.custom.css.placeholder": "/* 这里写自定义CSS */", + "display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏", + "display.sidebar.disabled": "隐藏的图标", + "display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里", + "display.sidebar.files.icon": "显示文件图标", + "display.sidebar.knowledge.icon": "显示知识图标", + "display.sidebar.minapp.icon": "显示小程序图标", + "display.sidebar.painting.icon": "显示绘画图标", + "display.sidebar.title": "侧边栏设置", + "display.sidebar.translate.icon": "显示翻译图标", + "display.sidebar.visible": "显示的图标", + "display.title": "显示设置", + "display.topic.title": "话题设置", + "miniapps": { + "title": "小程序设置", + "disabled": "隐藏的小程序", + "empty": "把要隐藏的小程序从左侧拖拽到这里", + "visible": "显示的小程序", + "cache_settings": "缓存设置", + "cache_title": "小程序缓存数量", + "cache_description": "设置同时保持活跃状态的小程序最大数量", + "reset_tooltip": "重置为默认值", + "display_title": "小程序显示设置", + "sidebar_title": "侧边栏活跃小程序显示设置", + "sidebar_description": "设置侧边栏是否显示活跃的小程序", + "cache_change_notice": "更改将在打开的小程序增减至设定值后生效" + }, + "font_size.title": "消息字体大小", + "general": "常规设置", + "general.avatar.reset": "重置头像", + "general.backup.button": "备份", + "general.backup.title": "数据备份与恢复", + "general.display.title": "显示设置", + "general.emoji_picker": "表情选择器", + "general.image_upload": "图片上传", + "general.auto_check_update.title": "自动检测更新", + "general.reset.button": "重置", + "general.reset.title": "重置数据", + "general.restore.button": "恢复", + "general.title": "常规设置", + "general.user_name": "用户名", + "general.user_name.placeholder": "请输入用户名", + "general.view_webdav_settings": "查看 WebDAV 设置", + "input.auto_translate_with_space": "快速敲击3次空格翻译", + "input.target_language": "目标语言", + "input.target_language.chinese": "简体中文", + "input.target_language.chinese-traditional": "繁体中文", + "input.target_language.english": "英文", + "input.target_language.japanese": "日文", + "input.target_language.russian": "俄文", + "launch.onboot": "开机自动启动", + "launch.title": "启动", + "launch.totray": "启动时最小化到托盘", + "mcp": { + "actions": "操作", + "active": "启用", + "addError": "添加服务器失败", + "addServer": "添加服务器", + "addSuccess": "服务器添加成功", + "args": "参数", + "argsTooltip": "每个参数占一行", + "baseUrlTooltip": "远程 URL 地址", + "command": "命令", + "sse": "服务器发送事件 (sse)", + "streamableHttp": "可流式传输的HTTP (streamableHttp)", + "stdio": "标准输入/输出 (stdio)", + "inMemory": "内存", + "config_description": "配置模型上下文协议服务器", + "deleteError": "删除服务器失败", + "deleteSuccess": "服务器删除成功", + "dependenciesInstall": "安装依赖项", + "dependenciesInstalling": "正在安装依赖项...", + "description": "描述", + "duplicateName": "已存在同名服务器", + "editJson": "编辑JSON", + "editServer": "编辑服务器", + "env": "环境变量", + "envTooltip": "格式:KEY=value,每行一个", + "findMore": "更多 MCP", + "searchNpx": "搜索 MCP", + "install": "安装", + "installError": "安装依赖项失败", + "installSuccess": "依赖项安装成功", + "jsonFormatError": "JSON格式化错误", + "jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。", + "jsonSaveError": "保存JSON配置失败", + "jsonSaveSuccess": "JSON配置已保存", + "missingDependencies": "缺失,请安装它以继续", + "name": "名称", + "noServers": "未配置服务器", + "newServer": "MCP 服务器", + "npx_list": { + "actions": "操作", + "desc": "搜索并添加 npm 包作为 MCP 服务", + "description": "描述", + "no_packages": "未找到包", + "npm": "NPM", + "package_name": "包名称", + "scope_placeholder": "输入 npm 作用域 (例如 @your-org)", + "scope_required": "请输入 npm 作用域", + "search": "搜索", + "search_error": "搜索失败", + "title": "NPX 包列表", + "usage": "用法", + "version": "版本" + }, + "errors": { + "32000": "MCP 服务器启动失败,请根据教程检查参数是否填写完整" + }, + "serverPlural": "服务器", + "serverSingular": "服务器", + "title": "MCP 服务器", + "startError": "启动失败", + "type": "类型", + "updateError": "更新服务器失败", + "updateSuccess": "服务器更新成功", + "url": "URL", + "editMcpJson": "编辑 MCP 配置", + "installHelp": "获取安装帮助", + "tools": { + "inputSchema": "输入参数", + "availableTools": "可用工具", + "noToolsAvailable": "没有可用工具" + }, + "deleteServer": "删除服务器", + "deleteServerConfirm": "确定要删除此服务器吗?", + "registry": "包管理源", + "registryTooltip": "选择用于安装包的源,以解决默认源的网络问题。", + "registryDefault": "默认", + "not_support": "模型不支持", + "user": "用户", + "system": "系统" + }, + "messages.divider": "消息分割线", + "messages.grid_columns": "消息网格展示列数", + "messages.grid_popover_trigger": "网格详情触发", + "messages.grid_popover_trigger.click": "点击显示", + "messages.grid_popover_trigger.hover": "悬停显示", + "messages.input.paste_long_text_as_file": "长文本粘贴为文件", + "messages.input.paste_long_text_threshold": "长文本长度", + "messages.input.send_shortcuts": "发送快捷键", + "messages.input.show_estimated_tokens": "显示预估 Token 数", + "messages.input.title": "输入设置", + "messages.input.enable_quick_triggers": "启用 '/' 和 '@' 触发快捷菜单", + "messages.markdown_rendering_input_message": "Markdown 渲染输入消息", + "messages.math_engine": "数学公式引擎", + "messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens", + "messages.model.title": "模型设置", + "messages.navigation": "对话导航按钮", + "messages.navigation.anchor": "对话锚点", + "messages.navigation.buttons": "上下按钮", + "messages.navigation.none": "不显示", + "messages.title": "消息设置", + "messages.use_serif_font": "使用衬线字体", + "model": "默认模型", + "models.add.add_model": "添加模型", + "models.add.group_name": "分组名称", + "models.add.group_name.placeholder": "例如 ChatGPT", + "models.add.group_name.tooltip": "例如 ChatGPT", + "models.add.model_id": "模型 ID", + "models.add.model_id.placeholder": "必填 例如 gpt-3.5-turbo", + "models.add.model_id.tooltip": "例如 gpt-3.5-turbo", + "models.add.model_name": "模型名称", + "models.add.model_name.placeholder": "例如 GPT-3.5", + "models.check.all": "所有", + "models.check.all_models_passed": "所有模型检查通过", + "models.check.button_caption": "健康检查", + "models.check.disabled": "关闭", + "models.check.enable_concurrent": "并发检查", + "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.no_api_keys": "未找到API密钥,请先添加API密钥。", + "models.check.passed": "通过", + "models.check.select_api_key": "选择要使用的API密钥:", + "models.check.single": "单个", + "models.check.start": "开始", + "models.check.title": "模型健康检查", + "models.check.use_all_keys": "使用密钥", + "models.default_assistant_model": "默认助手模型", + "models.default_assistant_model_description": "创建新助手时使用的模型,如果助手未设置模型,则使用此模型", + "models.empty": "没有模型", + "models.enable_topic_naming": "话题自动重命名", + "models.manage.add_whole_group": "添加整个分组", + "models.manage.remove_whole_group": "移除整个分组", + "models.topic_naming_model": "话题命名模型", + "models.topic_naming_model_description": "自动命名新话题时使用的模型", + "models.topic_naming_model_setting_title": "话题命名模型设置", + "models.topic_naming_prompt": "话题命名提示词", + "models.translate_model": "翻译模型", + "models.translate_model_description": "翻译服务使用的模型", + "models.translate_model_prompt_message": "请输入翻译模型提示词", + "models.translate_model_prompt_title": "翻译模型提示词", + "moresetting": "更多设置", + "moresetting.check.confirm": "确认勾选", + "moresetting.check.warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!!", + "moresetting.warn": "风险警告", + "provider": { + "add.name": "提供商名称", + "add.name.placeholder": "例如 OpenAI", + "add.title": "添加提供商", + "add.type": "提供商类型", + "api.url.preview": "预览: {{url}}", + "api.url.reset": "重置", + "api.url.tip": "/结尾忽略v1版本,#结尾强制使用输入地址", + "api_host": "API 地址", + "api_key": "API 密钥", + "api_key.tip": "多个密钥使用逗号分隔", + "api_version": "API 版本", + "charge": "充值", + "check": "检查", + "check_all_keys": "检查所有密钥", + "check_multiple_keys": "检查多个 API 密钥", + "copilot": { + "auth_failed": "Github Copilot 认证失败", + "auth_success": "Github Copilot 认证成功", + "auth_success_title": "认证成功", + "code_failed": "获取 Device Code 失败,请重试", + "code_generated_desc": "请将 Device Code 复制到下面的浏览器链接中", + "code_generated_title": "获取 Device Code", + "confirm_login": "过度使用可能会导致您的 Github 账号遭到封号,请谨慎使用!!!!", + "confirm_title": "风险警告", + "connect": "连接 Github", + "custom_headers": "自定义请求头", + "description": "您的 Github 账号需要订阅 Copilot", + "expand": "展开", + "headers_description": "自定义请求头(json格式)", + "invalid_json": "JSON 格式错误", + "login": "登录 Github", + "logout": "退出 Github", + "logout_failed": "退出失败,请重试", + "logout_success": "已成功退出", + "model_setting": "模型设置", + "open_verification_first": "请先点击上方链接访问验证页面", + "rate_limit": "速率限制", + "tooltip": "使用 Github Copilot 需要先登录 Github" + }, + "delete.content": "确定要删除此模型提供商吗?", + "delete.title": "删除提供商", + "docs_check": "查看", + "docs_more_details": "获取更多详情", + "get_api_key": "点击这里获取密钥", + "is_not_support_array_content": "开启兼容模式", + "no_models_for_check": "没有可以被检查的模型(例如对话模型)", + "not_checked": "未检查", + "remove_duplicate_keys": "移除重复密钥", + "remove_invalid_keys": "删除无效密钥", + "search": "搜索模型平台...", + "search_placeholder": "搜索模型 ID 或名称", + "title": "模型服务" + }, + "proxy": { + "mode": { + "custom": "自定义代理", + "none": "不使用代理", + "system": "系统代理", + "title": "代理模式" + }, + "title": "代理设置" + }, + "proxy.title": "代理地址", + "quickAssistant": { + "click_tray_to_show": "点击托盘图标启动", + "enable_quick_assistant": "启用快捷助手", + "read_clipboard_at_startup": "启动时读取剪贴板", + "title": "快捷助手", + "use_shortcut_to_show": "右键点击托盘图标或使用快捷键启动" + }, + "shortcuts": { + "action": "操作", + "clear_shortcut": "清除快捷键", + "clear_topic": "清空消息", + "copy_last_message": "复制上一条消息", + "key": "按键", + "mini_window": "快捷助手", + "new_topic": "新建话题", + "press_shortcut": "按下快捷键", + "reset_defaults": "重置默认快捷键", + "reset_defaults_confirm": "确定要重置所有快捷键吗?", + "reset_to_default": "重置为默认", + "search_message": "搜索消息", + "show_app": "显示/隐藏应用", + "show_settings": "打开设置", + "title": "快捷方式", + "toggle_new_context": "清除上下文", + "toggle_show_assistants": "切换助手显示", + "toggle_show_topics": "切换话题显示", + "zoom_in": "放大界面", + "zoom_out": "缩小界面", + "zoom_reset": "重置缩放" + }, + "theme.auto": "自动", + "theme.dark": "深色", + "theme.light": "浅色", + "theme.title": "主题", + "theme.window.style.opaque": "不透明窗口", + "theme.window.style.title": "窗口样式", + "theme.window.style.transparent": "透明窗口", + "title": "设置", + "topic.position": "话题位置", + "topic.position.left": "左侧", + "topic.position.right": "右侧", + "topic.show.time": "显示话题时间", + "tray.onclose": "关闭时最小化到托盘", + "tray.show": "显示托盘图标", + "tray.title": "托盘", + "websearch": { + "blacklist": "黑名单", + "blacklist_description": "在搜索结果中不会出现以下网站的结果", + "blacklist_tooltip": "请使用以下格式(换行分隔)\n匹配模式: *://*.example.com/*\n正则表达式: /example\\.(net|org)/", + "check": "检查", + "check_failed": "验证失败", + "check_success": "验证成功", + "enhance_mode": "搜索增强模式", + "enhance_mode_tooltip": "使用默认模型提取关键词后搜索", + "overwrite": "覆盖服务商搜索", + "overwrite_tooltip": "强制使用搜索服务商而不是大语言模型进行搜索", + "get_api_key": "点击这里获取密钥", + "no_provider_selected": "请选择搜索服务商后再检查", + "search_max_result": "搜索结果个数", + "search_provider": "搜索服务商", + "search_provider_placeholder": "选择一个搜索服务商", + "subscribe": "黑名单订阅", + "subscribe_update": "立即更新", + "subscribe_add": "添加订阅", + "subscribe_url": "订阅源地址", + "subscribe_name": "替代名字", + "subscribe_name.placeholder": "当下载的订阅源没有名称时所使用的替代名称", + "subscribe_add_success": "订阅源添加成功!", + "subscribe_delete": "删除订阅源", + "search_result_default": "默认", + "search_with_time": "搜索包含日期", + "tavily": { + "api_key": "Tavily API 密钥", + "api_key.placeholder": "请输入 Tavily API 密钥", + "description": "Tavily 是一个为 AI 代理量身定制的搜索引擎,提供实时、准确的结果、智能查询建议和深入的研究能力", + "title": "Tavily" + }, + "title": "网络搜索", + "apikey": "API 密钥", + "free": "免费" + }, + "quickPhrase": { + "title": "快捷短语", + "add": "添加短语", + "edit": "编辑短语", + "titleLabel": "标题", + "contentLabel": "内容", + "titlePlaceholder": "请输入短语标题", + "contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}。", + "delete": "删除短语", + "deleteConfirm": "删除短语后将无法恢复,是否继续?" + }, + "quickPanel": { + "title": "快捷菜单", + "close": "关闭", + "select": "选择", + "page": "翻页", + "confirm": "确认", + "back": "后退", + "forward": "前进", + "multiple": "多选" + }, + "privacy": { + "title": "隐私设置", + "enable_privacy_mode": "匿名发送错误报告和数据统计" + }, + "voice": { + "title": "语音功能", + "help": "语音功能包括文本转语音(TTS)、语音识别(ASR)和语音通话。", + "learn_more": "了解更多" + }, + "tts": { + "title": "语音合成", + "tab_title": "语音合成", + "enable": "启用语音合成", + "enable.help": "启用后可以将文本转换为语音", + "reset": "重置", + "reset_title": "重置自定义音色和模型", + "reset_confirm": "确定要重置所有自定义音色和模型吗?这将删除所有已添加的自定义项。", + "reset_success": "重置成功", + "reset_help": "如果音色或模型显示异常,可以尝试重置所有自定义项", + "api_settings": "API设置", + "service_type": "服务类型", + "service_type.openai": "OpenAI", + "service_type.edge": "浏览器 TTS", + "service_type.siliconflow": "硅基流动", + "service_type.mstts": "免费在线 TTS", + "service_type.refresh": "刷新TTS服务类型设置", + "service_type.refreshed": "已刷新TTS服务类型设置", + "siliconflow_api_key": "硅基流动API密钥", + "siliconflow_api_key.placeholder": "请输入硅基流动API密钥", + "siliconflow_api_url": "硅基流动API地址", + "siliconflow_api_url.placeholder": "例如:https://api.siliconflow.cn/v1/audio/speech", + "siliconflow_voice": "硅基流动音色", + "siliconflow_voice.placeholder": "请选择音色", + "siliconflow_model": "硅基流动模型", + "siliconflow_model.placeholder": "请选择模型", + "siliconflow_response_format": "响应格式", + "siliconflow_response_format.placeholder": "默认为mp3", + "siliconflow_speed": "语速", + "siliconflow_speed.placeholder": "默认为1.0", + "api_key": "API密钥", + "api_key.placeholder": "请输入OpenAI API密钥", + "api_url": "API地址", + "api_url.placeholder": "例如:https://api.openai.com/v1/audio/speech", + "edge_voice": "浏览器 TTS音色", + "edge_voice.loading": "加载中...", + "edge_voice.refresh": "刷新可用音色列表", + "edge_voice.not_found": "未找到匹配的音色", + "edge_voice.available_count": "可用语音: {{count}}个", + "edge_voice.refreshing": "正在刷新语音列表...", + "edge_voice.refreshed": "语音列表已刷新", + "mstts.voice": "免费在线 TTS音色", + "mstts.output_format": "输出格式", + "mstts.info": "免费在线TTS服务不需要API密钥,完全免费使用。", + "error.no_mstts_voice": "未设置免费在线 TTS音色", + "voice": "音色", + "voice.placeholder": "请选择音色", + "voice_input_placeholder": "输入音色", + "voice_add": "添加", + "voice_empty": "暂无自定义音色,请在下方添加", + "model": "模型", + "model.placeholder": "请选择模型", + "model_input_placeholder": "输入模型", + "model_add": "添加", + "model_empty": "暂无自定义模型,请在下方添加", + "filter_options": "过滤选项", + "filter.thinking_process": "过滤思考过程", + "filter.markdown": "过滤Markdown标记", + "filter.code_blocks": "过滤代码块", + "filter.html_tags": "过滤HTML标签", + "filter.emojis": "过滤表情符号", + "max_text_length": "最大文本长度", + "show_progress_bar": "显示TTS进度条", + "test": "测试语音", + "help": "语音合成功能支持将文本转换为自然语音。", + "learn_more": "了解更多", + "play": "播放语音", + "stop": "停止播放", + "speak": "播放语音", + "stop_global": "停止所有语音播放", + "stopped": "已停止语音播放", + "segmented": "分段", + "segmented_play": "分段播放", + "segmented_playback": "分段播放", + "error": { + "not_enabled": "语音合成功能未启用", + "no_api_key": "未设置API密钥", + "no_voice": "未选择音色", + "no_model": "未选择模型", + "no_edge_voice": "未选择浏览器 TTS音色", + "browser_not_support": "浏览器不支持语音合成", + "synthesis_failed": "语音合成失败", + "play_failed": "语音播放失败", + "empty_text": "文本为空", + "general": "语音合成出现错误", + "unsupported_service_type": "不支持的服务类型: {{serviceType}}" + } + }, + "asr": { + "title": "语音识别", + "tab_title": "语音识别", + "enable": "启用语音识别", + "enable.help": "启用后可以将语音转换为文本", + "service_type": "服务类型", + "service_type.browser": "浏览器", + "service_type.local": "本地服务器", + "api_key": "API密钥", + "api_key.placeholder": "请输入OpenAI API密钥", + "api_url": "API地址", + "api_url.placeholder": "例如:https://api.openai.com/v1/audio/transcriptions", + "model": "模型", + "browser.info": "使用浏览器内置的语音识别功能,无需额外设置", + "local.info": "使用本地服务器和浏览器进行语音识别,需要先启动服务器并打开浏览器页面", + "local.browser_tip": "请在浏览器中打开此页面,并保持浏览器窗口打开", + "local.test_connection": "测试连接", + "local.connection_success": "连接成功", + "local.connection_failed": "连接失败,请确保服务器已启动", + "server.start": "启动服务器", + "server.stop": "停止服务器", + "server.starting": "正在启动服务器...", + "server.started": "服务器已启动", + "server.stopping": "正在停止服务器...", + "server.stopped": "服务器已停止", + "server.already_running": "服务器已经在运行中", + "server.not_running": "服务器未运行", + "server.start_failed": "启动服务器失败", + "server.stop_failed": "停止服务器失败", + "open_browser": "打开浏览器页面", + "test": "测试语音识别", + "test_info": "请在输入框中使用语音识别按钮进行测试", + "start": "开始录音", + "stop": "停止录音", + "preparing": "准备中", + "recording": "正在录音...", + "processing": "正在处理语音...", + "success": "语音识别成功", + "completed": "语音识别完成", + "canceled": "已取消录音", + "auto_start_server": "启动应用自动开启服务器", + "auto_start_server.help": "启用后,应用启动时会自动开启语音识别服务器", + "error": { + "not_enabled": "语音识别功能未启用", + "no_api_key": "未设置API密钥", + "browser_not_support": "浏览器不支持语音识别", + "start_failed": "开始录音失败", + "transcribe_failed": "语音识别失败" + } + }, + "voice_call": { + "tab_title": "通话功能", + "enable": "启用语音通话", + "enable.help": "启用后可以使用语音通话功能与AI进行对话", + "model": "通话模型", + "model.select": "选择模型", + "model.current": "当前模型: {{model}}", + "model.info": "选择用于语音通话的AI模型,不同模型可能有不同的语音交互体验", + "welcome_message": "您好,我是您的AI助手,请长按说话按钮进行对话。", + "prompt": { + "label": "语音通话提示词", + "placeholder": "请输入语音通话提示词", + "save": "保存", + "reset": "重置", + "saved": "提示词已保存", + "reset_done": "提示词已重置", + "info": "此提示词将指导AI在语音通话模式下的回复方式" + }, + "asr_tts_info": "语音通话使用上面的语音识别(ASR)和语音合成(TTS)设置", + "test": "测试通话", + "test_info": "请使用输入框右侧的语音通话按钮进行测试" + } + }, + "translate": { + "any.language": "任意语言", + "button.translate": "翻译", + "close": "关闭", + "confirm": { + "content": "翻译后将覆盖原文,是否继续?", + "title": "翻译确认" + }, + "error.failed": "翻译失败", + "error.not_configured": "翻译模型未配置", + "history": { + "clear": "清空历史", + "clear_description": "清空历史将删除所有翻译历史记录,是否继续?", + "delete": "删除", + "empty": "暂无翻译历史", + "title": "翻译历史" + }, + "menu": { + "description": "对当前输入框内容进行翻译" + }, + "input.placeholder": "输入文本进行翻译", + "output.placeholder": "翻译", + "processing": "翻译中...", + "scroll_sync.disable": "关闭滚动同步", + "scroll_sync.enable": "开启滚动同步", + "title": "翻译", + "tooltip.newline": "换行" + }, + "tray": { + "quit": "退出", + "show_mini_window": "快捷助手", + "show_window": "显示窗口" + }, + "words": { + "knowledgeGraph": "知识图谱", + "quit": "退出", + "show_window": "显示窗口", + "visualization": "可视化" + } + } +} diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index def1809219..084143c7f8 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1421,7 +1421,15 @@ "mstts.voice": "[to be translated]:免费在线 TTS音色", "mstts.output_format": "[to be translated]:输出格式", "mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。", - "error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色" + "error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色", + "play": "[to be translated]:播放语音", + "stop": "[to be translated]:停止播放", + "speak": "[to be translated]:播放语音", + "stop_global": "[to be translated]:停止所有语音播放", + "stopped": "[to be translated]:已停止语音播放", + "segmented": "[to be translated]:分段", + "segmented_play": "[to be translated]:分段播放", + "segmented_playback": "[to be translated]:分段播放" }, "voice": { "title": "[to be translated]:语音功能", @@ -1474,7 +1482,9 @@ "browser_not_support": "[to be translated]:浏览器不支持语音识别", "start_failed": "[to be translated]:开始录音失败", "transcribe_failed": "[to be translated]:语音识别失败" - } + }, + "auto_start_server": "[to be translated]:启动应用自动开启服务器", + "auto_start_server.help": "[to be translated]:启用后,应用启动时会自动开启语音识别服务器" }, "voice_call": { "tab_title": "[to be translated]:通话功能", @@ -1484,9 +1494,19 @@ "model.select": "[to be translated]:选择模型", "model.current": "[to be translated]:当前模型: {{model}}", "model.info": "[to be translated]:选择用于语音通话的AI模型,不同模型可能有不同的语音交互体验", - "asr_tts_info": "[to be translated]:语音通话使用上面的语音识别(ASR)和语音合成(TTS)设置", - "test": "[to be translated]:测试通话", - "test_info": "[to be translated]:请使用输入框右侧的语音通话按钮进行测试" + "prompt": { + "label": "語音通話提示詞", + "placeholder": "請輸入語音通話提示詞", + "save": "保存", + "reset": "重置", + "saved": "提示詞已保存", + "reset_done": "提示詞已重置", + "info": "此提示詞將指導AI在語音通話模式下的回覆方式", + "language_info": "點擊重置按鈕可獲取當前語言的預設提示詞" + }, + "asr_tts_info": "語音通話使用上面的語音識別(ASR)和語音合成(TTS)設置", + "test": "測試通話", + "test_info": "請使用輸入框右側的語音通話按鈕進行測試" } }, "translate": { diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 7897ef2dda..57a7384a23 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -18,6 +18,7 @@ import { QuickPanelListItem, QuickPanelView, useQuickPanel } from '@renderer/com import TranslateButton from '@renderer/components/TranslateButton' import VoiceCallButton from '@renderer/components/VoiceCallButton' import { isGenerateImageModel, isVisionModel, isWebSearchModel } from '@renderer/config/models' +import { getDefaultVoiceCallPrompt } from '@renderer/config/prompts' import db from '@renderer/databases' import { useAssistant } from '@renderer/hooks/useAssistant' import { useKnowledgeBases } from '@renderer/hooks/useKnowledge' @@ -804,22 +805,17 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = ) } - // 添加语音通话专属提示词 - const voiceCallPrompt = `当前是语音通话模式。请注意: -1. 简洁直接地回答问题,避免冗长的引导和总结。 -2. 避免使用复杂的格式化内容,如表格、代码块、Markdown等。 -3. 使用自然、口语化的表达方式,就像与人对话一样。 -4. 如果需要列出要点,使用简单的数字或文字标记,而不是复杂的格式。 -5. 回答应该简短有力,便于用户通过语音理解。 -6. 避免使用特殊符号、表情符号、标点符号等,因为这些在语音播放时会影响理解。 -7. 使用完整的句子而非简单的关键词列表。 -8. 尽量使用常见词汇,避免生僻或专业术语,除非用户特别询问。` + // 获取用户自定义提示词 + const { voiceCallPrompt } = store.getState().settings + + // 使用自定义提示词或当前语言的默认提示词 + const promptToUse = voiceCallPrompt || getDefaultVoiceCallPrompt() // 如果助手已经有提示词,则在其后添加语音通话专属提示词 if (assistantToUse.prompt) { - assistantToUse.prompt += '\n\n' + voiceCallPrompt + assistantToUse.prompt += '\n\n' + promptToUse } else { - assistantToUse.prompt = voiceCallPrompt + assistantToUse.prompt = promptToUse } console.log('为语音通话消息添加了专属提示词') diff --git a/src/renderer/src/pages/settings/TTSSettings/ASRSettings.tsx b/src/renderer/src/pages/settings/TTSSettings/ASRSettings.tsx index ab5e7fa83c..5373bc5045 100644 --- a/src/renderer/src/pages/settings/TTSSettings/ASRSettings.tsx +++ b/src/renderer/src/pages/settings/TTSSettings/ASRSettings.tsx @@ -7,6 +7,7 @@ import { setAsrApiUrl, setAsrAutoStartServer, setAsrEnabled, + setAsrLanguage, setAsrModel, setAsrServiceType } from '@renderer/store/settings' @@ -30,6 +31,7 @@ const ASRSettings: FC = () => { const asrApiUrl = useSelector((state: any) => state.settings.asrApiUrl) const asrModel = useSelector((state: any) => state.settings.asrModel || 'whisper-1') const asrAutoStartServer = useSelector((state: any) => state.settings.asrAutoStartServer) + const asrLanguage = useSelector((state: any) => state.settings.asrLanguage || 'zh-CN') // 检查服务器状态 useEffect(() => { @@ -48,6 +50,20 @@ const ASRSettings: FC = () => { // 模型选项 const modelOptions = [{ label: 'whisper-1', value: 'whisper-1' }] + // 语言选项 + const languageOptions = [ + { label: '中文 (Chinese)', value: 'zh-CN' }, + { label: 'English', value: 'en-US' }, + { label: '日本語 (Japanese)', value: 'ja-JP' }, + { label: 'Русский (Russian)', value: 'ru-RU' }, + { label: 'Français (French)', value: 'fr-FR' }, + { label: 'Deutsch (German)', value: 'de-DE' }, + { label: 'Español (Spanish)', value: 'es-ES' }, + { label: 'Italiano (Italian)', value: 'it-IT' }, + { label: 'Português (Portuguese)', value: 'pt-PT' }, + { label: '한국어 (Korean)', value: 'ko-KR' } + ] + return (
@@ -154,7 +170,7 @@ const ASRSettings: FC = () => { @@ -187,6 +203,19 @@ const ASRSettings: FC = () => { {t('settings.asr.local.browser_tip')} + {/* 语言选择 */} + + { (ttsServiceType === 'openai' && (!ttsApiKey || !ttsVoice || !ttsModel)) || (ttsServiceType === 'edge' && !ttsEdgeVoice) || (ttsServiceType === 'siliconflow' && - (!ttsSiliconflowApiKey || !ttsSiliconflowVoice || !ttsSiliconflowModel)) + (!ttsSiliconflowApiKey || !ttsSiliconflowVoice || !ttsSiliconflowModel)) || + (ttsServiceType === 'mstts' && !ttsMsVoice) }> {t('settings.tts.test')} diff --git a/src/renderer/src/pages/settings/TTSSettings/VoiceCallSettings.tsx b/src/renderer/src/pages/settings/TTSSettings/VoiceCallSettings.tsx index e952bb3efe..dd24729eda 100644 --- a/src/renderer/src/pages/settings/TTSSettings/VoiceCallSettings.tsx +++ b/src/renderer/src/pages/settings/TTSSettings/VoiceCallSettings.tsx @@ -1,9 +1,10 @@ -import { InfoCircleOutlined, PhoneOutlined } from '@ant-design/icons' +import { InfoCircleOutlined, PhoneOutlined, ReloadOutlined } from '@ant-design/icons' import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import { getModelLogo } from '@renderer/config/models' +import { DEFAULT_VOICE_CALL_PROMPT } from '@renderer/config/prompts' import { useAppDispatch } from '@renderer/store' -import { setVoiceCallEnabled, setVoiceCallModel } from '@renderer/store/settings' -import { Button, Form, Space, Switch, Tooltip as AntTooltip } from 'antd' +import { setVoiceCallEnabled, setVoiceCallModel, setVoiceCallPrompt } from '@renderer/store/settings' +import { Button, Form, Input, Space, Switch, Tooltip as AntTooltip } from 'antd' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -16,6 +17,10 @@ const VoiceCallSettings: FC = () => { // 从 Redux 获取通话功能设置 const voiceCallEnabled = useSelector((state: any) => state.settings.voiceCallEnabled ?? true) const voiceCallModel = useSelector((state: any) => state.settings.voiceCallModel) + const voiceCallPrompt = useSelector((state: any) => state.settings.voiceCallPrompt) + + // 提示词编辑状态 + const [promptText, setPromptText] = useState(voiceCallPrompt || DEFAULT_VOICE_CALL_PROMPT) // 模型选择状态 const [, setIsSelectingModel] = useState(false) @@ -35,6 +40,19 @@ const VoiceCallSettings: FC = () => { } } + // 保存提示词 + const handleSavePrompt = () => { + dispatch(setVoiceCallPrompt(promptText)) + window.message.success({ content: t('settings.voice_call.prompt.saved'), key: 'voice-call-prompt' }) + } + + // 重置提示词 + const handleResetPrompt = () => { + setPromptText(DEFAULT_VOICE_CALL_PROMPT) + dispatch(setVoiceCallPrompt(null)) + window.message.success({ content: t('settings.voice_call.prompt.reset_done'), key: 'voice-call-prompt' }) + } + return ( @@ -71,6 +89,26 @@ const VoiceCallSettings: FC = () => { {t('settings.voice_call.model.info')} + {/* 提示词设置 */} + + setPromptText(e.target.value)} + disabled={!voiceCallEnabled} + rows={8} + placeholder={t('settings.voice_call.prompt.placeholder')} + /> + + + + + {t('settings.voice_call.prompt.info')} + + {/* ASR 和 TTS 设置提示 */} {t('settings.voice_call.asr_tts_info')} diff --git a/src/renderer/src/services/ASRServerService.ts b/src/renderer/src/services/ASRServerService.ts index 506715a4e8..e31b89bf32 100644 --- a/src/renderer/src/services/ASRServerService.ts +++ b/src/renderer/src/services/ASRServerService.ts @@ -14,13 +14,19 @@ class ASRServerService { startServer = async (): Promise => { if (this.isServerRunning) { console.log('[ASRServerService] 服务器已经在运行中') - window.message.info({ content: i18n.t('settings.asr.server.already_running'), key: 'asr-server' }) + // 安全地调用window.message + if (window.message) { + window.message.info({ content: i18n.t('settings.asr.server.already_running'), key: 'asr-server' }) + } return true } try { console.log('[ASRServerService] 正在启动ASR服务器...') - window.message.loading({ content: i18n.t('settings.asr.server.starting'), key: 'asr-server' }) + // 安全地调用window.message + if (window.message) { + window.message.loading({ content: i18n.t('settings.asr.server.starting'), key: 'asr-server' }) + } // 使用IPC调用主进程启动服务器 const result = await window.api.asrServer.startServer() @@ -29,22 +35,28 @@ class ASRServerService { this.isServerRunning = true this.serverProcess = result.pid console.log('[ASRServerService] ASR服务器启动成功,PID:', result.pid) - window.message.success({ content: i18n.t('settings.asr.server.started'), key: 'asr-server' }) + if (window.message) { + window.message.success({ content: i18n.t('settings.asr.server.started'), key: 'asr-server' }) + } return true } else { console.error('[ASRServerService] ASR服务器启动失败:', result.error) - window.message.error({ - content: i18n.t('settings.asr.server.start_failed') + ': ' + result.error, - key: 'asr-server' - }) + if (window.message) { + window.message.error({ + content: i18n.t('settings.asr.server.start_failed') + ': ' + result.error, + key: 'asr-server' + }) + } return false } } catch (error) { console.error('[ASRServerService] 启动ASR服务器时出错:', error) - window.message.error({ - content: i18n.t('settings.asr.server.start_failed') + ': ' + (error as Error).message, - key: 'asr-server' - }) + if (window.message) { + window.message.error({ + content: i18n.t('settings.asr.server.start_failed') + ': ' + (error as Error).message, + key: 'asr-server' + }) + } return false } } @@ -56,13 +68,17 @@ class ASRServerService { stopServer = async (): Promise => { if (!this.isServerRunning || !this.serverProcess) { console.log('[ASRServerService] 服务器未运行') - window.message.info({ content: i18n.t('settings.asr.server.not_running'), key: 'asr-server' }) + if (window.message) { + window.message.info({ content: i18n.t('settings.asr.server.not_running'), key: 'asr-server' }) + } return true } try { console.log('[ASRServerService] 正在停止ASR服务器...') - window.message.loading({ content: i18n.t('settings.asr.server.stopping'), key: 'asr-server' }) + if (window.message) { + window.message.loading({ content: i18n.t('settings.asr.server.stopping'), key: 'asr-server' }) + } // 使用IPC调用主进程停止服务器 const result = await window.api.asrServer.stopServer(this.serverProcess) @@ -71,22 +87,28 @@ class ASRServerService { this.isServerRunning = false this.serverProcess = null console.log('[ASRServerService] ASR服务器已停止') - window.message.success({ content: i18n.t('settings.asr.server.stopped'), key: 'asr-server' }) + if (window.message) { + window.message.success({ content: i18n.t('settings.asr.server.stopped'), key: 'asr-server' }) + } return true } else { console.error('[ASRServerService] ASR服务器停止失败:', result.error) - window.message.error({ - content: i18n.t('settings.asr.server.stop_failed') + ': ' + result.error, - key: 'asr-server' - }) + if (window.message) { + window.message.error({ + content: i18n.t('settings.asr.server.stop_failed') + ': ' + result.error, + key: 'asr-server' + }) + } return false } } catch (error) { console.error('[ASRServerService] 停止ASR服务器时出错:', error) - window.message.error({ - content: i18n.t('settings.asr.server.stop_failed') + ': ' + (error as Error).message, - key: 'asr-server' - }) + if (window.message) { + window.message.error({ + content: i18n.t('settings.asr.server.stop_failed') + ': ' + (error as Error).message, + key: 'asr-server' + }) + } return false } } @@ -104,7 +126,8 @@ class ASRServerService { * @returns string 网页URL */ getServerUrl = (): string => { - return 'http://localhost:8080' + console.log('[ASRServerService] 获取服务器URL: http://localhost:34515') + return 'http://localhost:34515' } /** diff --git a/src/renderer/src/services/ASRService.ts b/src/renderer/src/services/ASRService.ts index 5431040e55..143aa5185a 100644 --- a/src/renderer/src/services/ASRService.ts +++ b/src/renderer/src/services/ASRService.ts @@ -66,7 +66,7 @@ class ASRService { console.log('[ASRService] 正在连接WebSocket服务器...') window.message.loading({ content: '正在连接语音识别服务...', key: 'ws-connect' }) - this.ws = new WebSocket('ws://localhost:8080') + this.ws = new WebSocket('ws://localhost:34515') // 使用正确的端口 34515 this.wsConnected = false this.browserReady = false @@ -253,6 +253,9 @@ class ASRService { throw new Error('无法连接到语音识别服务') } + // 获取语言设置 + const { asrLanguage } = store.getState().settings + // 检查浏览器是否准备好 if (!this.browserReady) { // 尝试等待浏览器准备好 @@ -270,7 +273,7 @@ class ASRService { // 尝试自动打开浏览器页面 try { // 使用ASRServerService获取服务器URL - const serverUrl = 'http://localhost:8080' + const serverUrl = 'http://localhost:34515' // 使用正确的端口 34515 console.log('尝试打开语音识别服务器页面:', serverUrl) window.open(serverUrl, '_blank') } catch (error) { @@ -302,9 +305,15 @@ class ASRService { // 发送开始命令 if (this.ws && this.wsConnected) { - this.ws.send(JSON.stringify({ type: 'start' })) + // 将语言设置传递给服务器 + this.ws.send( + JSON.stringify({ + type: 'start', + language: asrLanguage || 'zh-CN' // 使用设置的语言或默认中文 + }) + ) this.isRecording = true - console.log('开始语音识别') + console.log('开始语音识别,语言:', asrLanguage || 'zh-CN') window.message.info({ content: i18n.t('settings.asr.recording'), key: 'asr-recording' }) } else { throw new Error('WebSocket连接未就绪') @@ -638,7 +647,7 @@ class ASRService { */ openBrowserPage = (): void => { // 使用window.open打开浏览器页面 - window.open('http://localhost:8080', '_blank') + window.open('http://localhost:34515', '_blank') // 使用正确的端口 34515 } } diff --git a/src/renderer/src/services/VoiceCallService.ts b/src/renderer/src/services/VoiceCallService.ts index ecf6836715..5742500e61 100644 --- a/src/renderer/src/services/VoiceCallService.ts +++ b/src/renderer/src/services/VoiceCallService.ts @@ -1,3 +1,4 @@ +import { DEFAULT_VOICE_CALL_PROMPT } from '@renderer/config/prompts' import { fetchChatCompletion } from '@renderer/services/ApiService' import ASRService from '@renderer/services/ASRService' import { getDefaultAssistant } from '@renderer/services/AssistantService' @@ -5,8 +6,10 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService' import TTSService from '@renderer/services/TTSService' import store from '@renderer/store' +import { setSkipNextAutoTTS } from '@renderer/store/settings' // 导入类型 import type { Message } from '@renderer/types' +import i18n from 'i18next' interface VoiceCallCallbacks { onTranscript: (text: string) => void @@ -170,9 +173,13 @@ class VoiceCallServiceClass { } } - // 播放欢迎语音 - const welcomeMessage = '您好,我是您的AI助手,请长按说话按钮进行对话。' - this.callbacks?.onResponse(welcomeMessage) + // 设置skipNextAutoTTS为true,防止自动播放最后一条消息 + store.dispatch(setSkipNextAutoTTS(true)) + + // 播放欢迎语音 - 根据当前语言获取本地化的欢迎消息 + const welcomeMessage = i18n.t('settings.voice_call.welcome_message') + // 不调用onResponse,避免触发两次TTS播放 + // this.callbacks?.onResponse(welcomeMessage) // 监听TTS状态 const ttsStateHandler = (isPlaying: boolean) => { @@ -583,21 +590,16 @@ class VoiceCallServiceClass { } }) - // 修改用户消息,添加语音通话提示 - const voiceCallPrompt = `当前是语音通话模式。请注意: -1. 简洁直接地回答问题,避免冗长的引导和总结。 -2. 避免使用复杂的格式化内容,如表格、代码块、Markdown等。 -3. 使用自然、口语化的表达方式,就像与人对话一样。 -4. 如果需要列出要点,使用简单的数字或文字标记,而不是复杂的格式。 -5. 回答应该简短有力,便于用户通过语音理解。 -6. 避免使用特殊符号、表情符号、标点符号等,因为这些在语音播放时会影响理解。 -7. 使用完整的句子而非简单的关键词列表。 -8. 尽量使用常见词汇,避免生僻或专业术语,除非用户特别询问。` + // 获取用户自定义提示词 + const { voiceCallPrompt } = store.getState().settings + + // 使用自定义提示词或默认提示词 + const promptToUse = voiceCallPrompt || DEFAULT_VOICE_CALL_PROMPT // 创建系统指令消息 const systemMessage = { role: 'system', - content: voiceCallPrompt + content: promptToUse } // 修改用户消息的内容 @@ -646,8 +648,12 @@ class VoiceCallServiceClass { // 添加事件监听器 window.addEventListener('tts-state-change', handleTTSStateChange as EventListener) - // 开始播放 - this.ttsService.speak(fullResponse) + // 更新助手消息的内容 + assistantMessage.content = fullResponse + assistantMessage.status = 'success' + + // 使用speakFromMessage方法播放,会应用TTS过滤选项 + this.ttsService.speakFromMessage(assistantMessage) // 设置超时安全机制,确保事件监听器被移除 setTimeout(() => { @@ -666,7 +672,17 @@ class VoiceCallServiceClass { if (!this.isMuted && this.isCallActive) { // 手动设置语音状态 this.callbacks?.onSpeakingStateChange(true) - this.ttsService.speak(fullResponse) + + // 创建一个简单的助手消息对象 + const errorMessage = { + id: 'error-message', + role: 'assistant', + content: fullResponse, + status: 'success' + } as Message + + // 使用speakFromMessage方法播放,会应用TTS过滤选项 + this.ttsService.speakFromMessage(errorMessage) // 确保语音结束后状态正确 setTimeout(() => { diff --git a/src/renderer/src/services/tts/MsTTSService.ts b/src/renderer/src/services/tts/MsTTSService.ts index 7925ace53d..0551f51573 100644 --- a/src/renderer/src/services/tts/MsTTSService.ts +++ b/src/renderer/src/services/tts/MsTTSService.ts @@ -1,5 +1,3 @@ -import i18n from '@renderer/i18n' - import { TTSServiceInterface } from './TTSServiceInterface' /** @@ -27,7 +25,15 @@ export class MsTTSService implements TTSServiceInterface { */ private validateParams(): void { if (!this.voice) { - throw new Error(i18n.t('settings.tts.error.no_mstts_voice')) + // 如果没有设置音色,使用默认的小晓音色 + console.warn('未设置免费在线TTS音色,使用默认音色 zh-CN-XiaoxiaoNeural') + this.voice = 'zh-CN-XiaoxiaoNeural' + } + + if (!this.outputFormat) { + // 如果没有设置输出格式,使用默认格式 + console.warn('未设置免费在线TTS输出格式,使用默认格式 audio-24khz-48kbitrate-mono-mp3') + this.outputFormat = 'audio-24khz-48kbitrate-mono-mp3' } } diff --git a/src/renderer/src/services/tts/TTSService.ts b/src/renderer/src/services/tts/TTSService.ts index d20c12577f..e982bc1397 100644 --- a/src/renderer/src/services/tts/TTSService.ts +++ b/src/renderer/src/services/tts/TTSService.ts @@ -104,6 +104,7 @@ export class TTSService { filterMarkdown: true, filterCodeBlocks: true, filterHtmlTags: true, + filterEmojis: true, maxTextLength: 4000 } @@ -592,7 +593,7 @@ export class TTSService { // 触发进度更新事件 this.emitProgressUpdateEvent(currentTime, duration, progress) } - }, 100) + }, 250) // 将更新频率从100ms降低到250ms,减少日志输出 } /** @@ -611,6 +612,11 @@ export class TTSService { * @param duration 总时长(秒) * @param progress 进度百分比(0-100) */ + // 记录上次输出日志的进度百分比 - 已禁用日志输出 + // private lastLoggedProgress: number = -1; + // 记录上次日志输出时间,用于节流 - 已禁用日志输出 + // private lastLogTime: number = 0; + private emitProgressUpdateEvent(currentTime: number, duration: number, progress: number): void { // 创建事件数据 const eventData = { @@ -621,12 +627,21 @@ export class TTSService { progress } - console.log('发送TTS进度更新事件:', { - messageId: this.playingMessageId, - progress: Math.round(progress), - currentTime: Math.round(currentTime), - duration: Math.round(duration) - }) + // 完全关闭进度更新日志输出 + // const now = Date.now(); + // const currentProgressTens = Math.floor(progress / 10); + // if ((now - this.lastLogTime >= 500) && // 时间节流 + // (currentProgressTens !== Math.floor(this.lastLoggedProgress / 10) || + // progress === 0 || progress >= 100)) { + // console.log('发送TTS进度更新事件:', { + // messageId: this.playingMessageId ? this.playingMessageId.substring(0, 8) : null, + // progress: Math.round(progress), + // currentTime: Math.round(currentTime), + // duration: Math.round(duration) + // }); + // this.lastLoggedProgress = progress; + // this.lastLogTime = now; + // } // 触发事件 window.dispatchEvent(new CustomEvent('tts-progress-update', { detail: eventData })) diff --git a/src/renderer/src/services/tts/TTSServiceFactory.ts b/src/renderer/src/services/tts/TTSServiceFactory.ts index 9d8fb0c4c7..215e5d40f1 100644 --- a/src/renderer/src/services/tts/TTSServiceFactory.ts +++ b/src/renderer/src/services/tts/TTSServiceFactory.ts @@ -48,14 +48,17 @@ export class TTSServiceFactory { settings.ttsSiliconflowSpeed ) - case 'mstts': + case 'mstts': { console.log('创建免费在线TTS服务实例') + // 确保音色有默认值 + const msVoice = settings.ttsMsVoice || 'zh-CN-XiaoxiaoNeural' + const msOutputFormat = settings.ttsMsOutputFormat || 'audio-24khz-48kbitrate-mono-mp3' console.log('免费在线TTS设置:', { - voice: settings.ttsMsVoice, - outputFormat: settings.ttsMsOutputFormat + voice: msVoice, + outputFormat: msOutputFormat }) - return new MsTTSService(settings.ttsMsVoice, settings.ttsMsOutputFormat) - + return new MsTTSService(msVoice, msOutputFormat) + } // Close block scope default: throw new Error(i18n.t('settings.tts.error.unsupported_service_type', { serviceType })) } diff --git a/src/renderer/src/services/tts/TTSTextFilter.ts b/src/renderer/src/services/tts/TTSTextFilter.ts index 023d3b93a1..72962e2951 100644 --- a/src/renderer/src/services/tts/TTSTextFilter.ts +++ b/src/renderer/src/services/tts/TTSTextFilter.ts @@ -16,6 +16,7 @@ export class TTSTextFilter { filterMarkdown: boolean filterCodeBlocks: boolean filterHtmlTags: boolean + filterEmojis: boolean maxTextLength: number } ): string { @@ -43,6 +44,11 @@ export class TTSTextFilter { filteredText = this.filterHtmlTags(filteredText) } + // 过滤表情符号 + if (options.filterEmojis) { + filteredText = this.filterEmojis(filteredText) + } + // 限制文本长度 if (options.maxTextLength > 0 && filteredText.length > options.maxTextLength) { filteredText = filteredText.substring(0, options.maxTextLength) @@ -145,4 +151,18 @@ export class TTSTextFilter { return text } + + /** + * 过滤表情符号 + * @param text 原始文本 + * @returns 过滤后的文本 + */ + private static filterEmojis(text: string): string { + // 过滤Unicode表情符号 + // 这个正则表达式匹配大多数常见的表情符号 + return text.replace( + /[\u{1F300}-\u{1F5FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, + '' + ) + } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 6377049dd3..f8942fae36 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -119,6 +119,7 @@ export interface SettingsState { ttsModel: string ttsCustomVoices: string[] ttsCustomModels: string[] + showTTSProgressBar: boolean // 是否显示TTS进度条 // 浏览器 TTS配置 ttsEdgeVoice: string // 硅基流动 TTS配置 @@ -137,6 +138,7 @@ export interface SettingsState { filterMarkdown: boolean // 过滤Markdown标记 filterCodeBlocks: boolean // 过滤代码块 filterHtmlTags: boolean // 过滤HTML标签 + filterEmojis: boolean // 过滤表情符号 maxTextLength: number // 最大文本长度 } // ASR配置(语音识别) @@ -146,9 +148,11 @@ export interface SettingsState { asrApiUrl: string asrModel: string asrAutoStartServer: boolean // 启动应用时自动启动ASR服务器 + asrLanguage: string // 语音识别语言 // 语音通话配置 voiceCallEnabled: boolean voiceCallModel: Model | null + voiceCallPrompt: string | null // 语音通话自定义提示词 isVoiceCallActive: boolean // 语音通话窗口是否激活 lastPlayedMessageId: string | null // 最后一次播放的消息ID skipNextAutoTTS: boolean // 是否跳过下一次自动TTS @@ -262,6 +266,7 @@ export const initialState: SettingsState = { ttsModel: '', ttsCustomVoices: [], ttsCustomModels: [], + showTTSProgressBar: true, // 默认显示TTS进度条 // Edge TTS配置 ttsEdgeVoice: 'zh-CN-XiaoxiaoNeural', // 默认使用小小的声音 // 硅基流动 TTS配置 @@ -279,6 +284,7 @@ export const initialState: SettingsState = { filterMarkdown: true, // 默认过滤Markdown标记 filterCodeBlocks: true, // 默认过滤代码块 filterHtmlTags: true, // 默认过滤HTML标签 + filterEmojis: true, // 默认过滤表情符号 maxTextLength: 4000 // 默认最大文本长度 }, // ASR配置(语音识别) @@ -288,9 +294,11 @@ export const initialState: SettingsState = { asrApiUrl: 'https://api.openai.com/v1/audio/transcriptions', asrModel: 'whisper-1', asrAutoStartServer: false, // 默认不自动启动ASR服务器 + asrLanguage: 'zh-CN', // 默认使用中文 // 语音通话配置 voiceCallEnabled: true, voiceCallModel: null, + voiceCallPrompt: null, // 默认为null,表示使用默认提示词 isVoiceCallActive: false, // 语音通话窗口是否激活 lastPlayedMessageId: null, // 最后一次播放的消息ID skipNextAutoTTS: false, // 是否跳过下一次自动TTS @@ -692,6 +700,7 @@ const settingsSlice = createSlice({ filterMarkdown?: boolean filterCodeBlocks?: boolean filterHtmlTags?: boolean + filterEmojis?: boolean maxTextLength?: number }> ) => { @@ -700,6 +709,10 @@ const settingsSlice = createSlice({ ...action.payload } }, + // 设置是否显示TTS进度条 + setShowTTSProgressBar: (state, action: PayloadAction) => { + state.showTTSProgressBar = action.payload + }, // ASR相关的action setAsrEnabled: (state, action: PayloadAction) => { state.asrEnabled = action.payload @@ -719,12 +732,18 @@ const settingsSlice = createSlice({ setAsrAutoStartServer: (state, action: PayloadAction) => { state.asrAutoStartServer = action.payload }, + setAsrLanguage: (state, action: PayloadAction) => { + state.asrLanguage = action.payload + }, setVoiceCallEnabled: (state, action: PayloadAction) => { state.voiceCallEnabled = action.payload }, setVoiceCallModel: (state, action: PayloadAction) => { state.voiceCallModel = action.payload }, + setVoiceCallPrompt: (state, action: PayloadAction) => { + state.voiceCallPrompt = action.payload + }, setIsVoiceCallActive: (state, action: PayloadAction) => { state.isVoiceCallActive = action.payload }, @@ -851,14 +870,17 @@ export const { removeTtsCustomVoice, removeTtsCustomModel, setTtsFilterOptions, + setShowTTSProgressBar, setAsrEnabled, setAsrServiceType, setAsrApiKey, setAsrApiUrl, setAsrModel, setAsrAutoStartServer, + setAsrLanguage, setVoiceCallEnabled, setVoiceCallModel, + setVoiceCallPrompt, setIsVoiceCallActive, setLastPlayedMessageId, setSkipNextAutoTTS diff --git a/yarn.lock b/yarn.lock index 19315811d5..bf0b27494c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3945,6 +3945,7 @@ __metadata: axios: "npm:^1.7.3" babel-plugin-styled-components: "npm:^2.1.4" browser-image-compression: "npm:^2.0.2" + bufferutil: "npm:^4.0.9" color: "npm:^5.0.0" dayjs: "npm:^1.11.11" dexie: "npm:^4.0.8" @@ -4021,9 +4022,11 @@ __metadata: turndown-plugin-gfm: "npm:^1.0.2" typescript: "npm:^5.6.2" undici: "npm:^7.4.0" + utf-8-validate: "npm:^6.0.5" uuid: "npm:^10.0.0" vite: "npm:^5.0.12" webdav: "npm:^5.8.0" + ws: "npm:^8.18.1" zipread: "npm:^1.3.3" languageName: unknown linkType: soft @@ -4938,6 +4941,16 @@ __metadata: languageName: node linkType: hard +"bufferutil@npm:^4.0.9": + version: 4.0.9 + resolution: "bufferutil@npm:4.0.9" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10c0/f8a93279fc9bdcf32b42eba97edc672b39ca0fe5c55a8596099886cffc76ea9dd78e0f6f51ecee3b5ee06d2d564aa587036b5d4ea39b8b5ac797262a363cdf7d + languageName: node + linkType: hard + "builder-util-runtime@npm:9.2.4": version: 9.2.4 resolution: "builder-util-runtime@npm:9.2.4" @@ -12107,6 +12120,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:^4.3.0": + version: 4.8.4 + resolution: "node-gyp-build@npm:4.8.4" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10c0/444e189907ece2081fe60e75368784f7782cfddb554b60123743dfb89509df89f1f29c03bbfa16b3a3e0be3f48799a4783f487da6203245fa5bed239ba7407e1 + languageName: node + linkType: hard + "node-gyp@npm:^9.1.0": version: 9.4.1 resolution: "node-gyp@npm:9.4.1" @@ -16635,6 +16659,16 @@ __metadata: languageName: node linkType: hard +"utf-8-validate@npm:^6.0.5": + version: 6.0.5 + resolution: "utf-8-validate@npm:6.0.5" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10c0/6dc63c513adb001e47a51819072cdd414158430091c49c21d4947ea99f16df5167b671f680df8fb2b6f2ae6a7f30264b4ec111bd3e573720dfe371da1ab99a81 + languageName: node + linkType: hard + "utf8-byte-length@npm:^1.0.1": version: 1.0.5 resolution: "utf8-byte-length@npm:1.0.5" @@ -17069,7 +17103,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.13.0, ws@npm:^8.14.1, ws@npm:^8.18.0": +"ws@npm:^8.13.0, ws@npm:^8.14.1, ws@npm:^8.18.0, ws@npm:^8.18.1": version: 8.18.1 resolution: "ws@npm:8.18.1" peerDependencies: