mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-12-05 15:56:13 +08:00
Compare commits
5 Commits
v2.14.2
...
feat-multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f271e1654a | ||
|
|
f6d4ae6fc0 | ||
|
|
a902f1abac | ||
|
|
da0ca997c1 | ||
|
|
f871c67da7 |
@@ -1,20 +1,21 @@
|
|||||||
|
|
||||||
# Your openai api key. (required)
|
# Your openai api key. (required)
|
||||||
OPENAI_API_KEY=sk-xxxx
|
OPENAI_API_KEY=sk-xxxx
|
||||||
|
|
||||||
# Access password, separated by comma. (optional)
|
# Access password, separated by comma. (optional)
|
||||||
CODE=your-password
|
CODE=your-password
|
||||||
|
|
||||||
# You can start service behind a proxy. (optional)
|
# You can start service behind a proxy
|
||||||
PROXY_URL=http://localhost:7890
|
PROXY_URL=http://localhost:7890
|
||||||
|
|
||||||
# (optional)
|
# (optional)
|
||||||
# Default: Empty
|
# Default: Empty
|
||||||
# Google Gemini Pro API key, set if you want to use Google Gemini Pro API.
|
# Googel Gemini Pro API key, set if you want to use Google Gemini Pro API.
|
||||||
GOOGLE_API_KEY=
|
GOOGLE_API_KEY=
|
||||||
|
|
||||||
# (optional)
|
# (optional)
|
||||||
# Default: https://generativelanguage.googleapis.com/
|
# Default: https://generativelanguage.googleapis.com/
|
||||||
# Google Gemini Pro API url without pathname, set if you want to customize Google Gemini Pro API url.
|
# Googel Gemini Pro API url without pathname, set if you want to customize Google Gemini Pro API url.
|
||||||
GOOGLE_URL=
|
GOOGLE_URL=
|
||||||
|
|
||||||
# Override openai api request base url. (optional)
|
# Override openai api request base url. (optional)
|
||||||
@@ -46,24 +47,3 @@ ENABLE_BALANCE_QUERY=
|
|||||||
# If you want to disable parse settings from url, set this value to 1.
|
# If you want to disable parse settings from url, set this value to 1.
|
||||||
DISABLE_FAST_LINK=
|
DISABLE_FAST_LINK=
|
||||||
|
|
||||||
# (optional)
|
|
||||||
# Default: Empty
|
|
||||||
# To control custom models, use + to add a custom model, use - to hide a model, use name=displayName to customize model name, separated by comma.
|
|
||||||
CUSTOM_MODELS=
|
|
||||||
|
|
||||||
# (optional)
|
|
||||||
# Default: Empty
|
|
||||||
# Change default model
|
|
||||||
DEFAULT_MODEL=
|
|
||||||
|
|
||||||
# anthropic claude Api Key.(optional)
|
|
||||||
ANTHROPIC_API_KEY=
|
|
||||||
|
|
||||||
### anthropic claude Api version. (optional)
|
|
||||||
ANTHROPIC_API_VERSION=
|
|
||||||
|
|
||||||
### anthropic claude Api url (optional)
|
|
||||||
ANTHROPIC_URL=
|
|
||||||
|
|
||||||
### (optional)
|
|
||||||
WHITE_WEBDEV_ENDPOINTS=
|
|
||||||
80
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
80
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,80 +0,0 @@
|
|||||||
name: '🐛 Bug Report'
|
|
||||||
description: 'Report an bug'
|
|
||||||
title: '[Bug] '
|
|
||||||
labels: ['bug']
|
|
||||||
body:
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '📦 Deployment Method'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- 'Official installation package'
|
|
||||||
- 'Vercel'
|
|
||||||
- 'Zeabur'
|
|
||||||
- 'Sealos'
|
|
||||||
- 'Netlify'
|
|
||||||
- 'Docker'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 Version'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '💻 Operating System'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- 'Windows'
|
|
||||||
- 'macOS'
|
|
||||||
- 'Ubuntu'
|
|
||||||
- 'Other Linux'
|
|
||||||
- 'iOS'
|
|
||||||
- 'iPad OS'
|
|
||||||
- 'Android'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 System Version'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '🌐 Browser'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- 'Chrome'
|
|
||||||
- 'Edge'
|
|
||||||
- 'Safari'
|
|
||||||
- 'Firefox'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 Browser Version'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🐛 Bug Description'
|
|
||||||
description: A clear and concise description of the bug, if the above option is `Other`, please also explain in detail.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📷 Recurrence Steps'
|
|
||||||
description: A clear and concise description of how to recurrence.
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🚦 Expected Behavior'
|
|
||||||
description: A clear and concise description of what you expected to happen.
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📝 Additional Information'
|
|
||||||
description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
|
|
||||||
80
.github/ISSUE_TEMPLATE/1_bug_report_cn.yml
vendored
80
.github/ISSUE_TEMPLATE/1_bug_report_cn.yml
vendored
@@ -1,80 +0,0 @@
|
|||||||
name: '🐛 反馈缺陷'
|
|
||||||
description: '反馈一个问题/缺陷'
|
|
||||||
title: '[Bug] '
|
|
||||||
labels: ['bug']
|
|
||||||
body:
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '📦 部署方式'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- '官方安装包'
|
|
||||||
- 'Vercel'
|
|
||||||
- 'Zeabur'
|
|
||||||
- 'Sealos'
|
|
||||||
- 'Netlify'
|
|
||||||
- 'Docker'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 软件版本'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '💻 系统环境'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- 'Windows'
|
|
||||||
- 'macOS'
|
|
||||||
- 'Ubuntu'
|
|
||||||
- 'Other Linux'
|
|
||||||
- 'iOS'
|
|
||||||
- 'iPad OS'
|
|
||||||
- 'Android'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 系统版本'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: '🌐 浏览器'
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- 'Chrome'
|
|
||||||
- 'Edge'
|
|
||||||
- 'Safari'
|
|
||||||
- 'Firefox'
|
|
||||||
- 'Other'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '📌 浏览器版本'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🐛 问题描述'
|
|
||||||
description: 请提供一个清晰且简洁的问题描述,若上述选项为`Other`,也请详细说明。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📷 复现步骤'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明如何复现问题。
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🚦 期望结果'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明您期望发生什么。
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📝 补充信息'
|
|
||||||
description: 如果您的问题需要进一步说明,或者您遇到的问题无法在一个简单的示例中复现,请在这里添加更多信息。
|
|
||||||
21
.github/ISSUE_TEMPLATE/2_feature_request.yml
vendored
21
.github/ISSUE_TEMPLATE/2_feature_request.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: '🌠 Feature Request'
|
|
||||||
description: 'Suggest an idea'
|
|
||||||
title: '[Feature Request] '
|
|
||||||
labels: ['enhancement']
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🥰 Feature Description'
|
|
||||||
description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🧐 Proposed Solution'
|
|
||||||
description: Describe the solution you'd like in a clear and concise manner.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📝 Additional Information'
|
|
||||||
description: Add any other context about the problem here.
|
|
||||||
21
.github/ISSUE_TEMPLATE/2_feature_request_cn.yml
vendored
21
.github/ISSUE_TEMPLATE/2_feature_request_cn.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: '🌠 功能需求'
|
|
||||||
description: '提出需求或建议'
|
|
||||||
title: '[Feature Request] '
|
|
||||||
labels: ['enhancement']
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🥰 需求描述'
|
|
||||||
description: 请添加一个清晰且简洁的问题描述,阐述您希望通过这个功能需求解决的问题。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🧐 解决方案'
|
|
||||||
description: 请清晰且简洁地描述您想要的解决方案。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '📝 补充信息'
|
|
||||||
description: 在这里添加关于问题的任何其他背景信息。
|
|
||||||
146
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
146
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: ["bug"]
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe the bug"
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: "Bug Description"
|
||||||
|
description: "A clear and concise description of what the bug is."
|
||||||
|
placeholder: "Explain the bug..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## To Reproduce"
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: "Steps to Reproduce"
|
||||||
|
description: "Steps to reproduce the behavior:"
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Expected behavior"
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: "Expected Behavior"
|
||||||
|
description: "A clear and concise description of what you expected to happen."
|
||||||
|
placeholder: "Describe what you expected to happen..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Screenshots"
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: "Screenshots"
|
||||||
|
description: "If applicable, add screenshots to help explain your problem."
|
||||||
|
placeholder: "Paste your screenshots here or write 'N/A' if not applicable..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Deployment"
|
||||||
|
- type: checkboxes
|
||||||
|
id: deployment
|
||||||
|
attributes:
|
||||||
|
label: "Deployment Method"
|
||||||
|
description: "Please select the deployment method you are using."
|
||||||
|
options:
|
||||||
|
- label: "Docker"
|
||||||
|
- label: "Vercel"
|
||||||
|
- label: "Server"
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Desktop (please complete the following information):"
|
||||||
|
- type: input
|
||||||
|
id: desktop-os
|
||||||
|
attributes:
|
||||||
|
label: "Desktop OS"
|
||||||
|
description: "Your desktop operating system."
|
||||||
|
placeholder: "e.g., Windows 10"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: desktop-browser
|
||||||
|
attributes:
|
||||||
|
label: "Desktop Browser"
|
||||||
|
description: "Your desktop browser."
|
||||||
|
placeholder: "e.g., Chrome, Safari"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: desktop-version
|
||||||
|
attributes:
|
||||||
|
label: "Desktop Browser Version"
|
||||||
|
description: "Version of your desktop browser."
|
||||||
|
placeholder: "e.g., 89.0"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Smartphone (please complete the following information):"
|
||||||
|
- type: input
|
||||||
|
id: smartphone-device
|
||||||
|
attributes:
|
||||||
|
label: "Smartphone Device"
|
||||||
|
description: "Your smartphone device."
|
||||||
|
placeholder: "e.g., iPhone X"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: smartphone-os
|
||||||
|
attributes:
|
||||||
|
label: "Smartphone OS"
|
||||||
|
description: "Your smartphone operating system."
|
||||||
|
placeholder: "e.g., iOS 14.4"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: smartphone-browser
|
||||||
|
attributes:
|
||||||
|
label: "Smartphone Browser"
|
||||||
|
description: "Your smartphone browser."
|
||||||
|
placeholder: "e.g., Safari"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: smartphone-version
|
||||||
|
attributes:
|
||||||
|
label: "Smartphone Browser Version"
|
||||||
|
description: "Version of your smartphone browser."
|
||||||
|
placeholder: "e.g., 14"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Additional Logs"
|
||||||
|
- type: textarea
|
||||||
|
id: additional-logs
|
||||||
|
attributes:
|
||||||
|
label: "Additional Logs"
|
||||||
|
description: "Add any logs about the problem here."
|
||||||
|
placeholder: "Paste any relevant logs here..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
53
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
53
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: "[Feature Request]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Is your feature request related to a problem? Please describe."
|
||||||
|
- type: textarea
|
||||||
|
id: problem-description
|
||||||
|
attributes:
|
||||||
|
label: Problem Description
|
||||||
|
description: "A clear and concise description of what the problem is. Example: I'm always frustrated when [...]"
|
||||||
|
placeholder: "Explain the problem you are facing..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe the solution you'd like"
|
||||||
|
- type: textarea
|
||||||
|
id: desired-solution
|
||||||
|
attributes:
|
||||||
|
label: Solution Description
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
placeholder: "Describe the solution you'd like..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe alternatives you've considered"
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives-considered
|
||||||
|
attributes:
|
||||||
|
label: Alternatives Considered
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
placeholder: "Describe any alternative solutions or features you've considered..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Additional context"
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
||||||
|
placeholder: "Add any other context or screenshots about the feature request here..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,28 +0,0 @@
|
|||||||
#### 💻 变更类型 | Change Type
|
|
||||||
|
|
||||||
<!-- For change type, change [ ] to [x]. -->
|
|
||||||
|
|
||||||
- [ ] feat <!-- 引入新功能 | Introduce new features -->
|
|
||||||
- [ ] fix <!-- 修复 Bug | Fix a bug -->
|
|
||||||
- [ ] refactor <!-- 重构代码(既不修复 Bug 也不添加新功能) | Refactor code that neither fixes a bug nor adds a feature -->
|
|
||||||
- [ ] perf <!-- 提升性能的代码变更 | A code change that improves performance -->
|
|
||||||
- [ ] style <!-- 添加或更新不影响代码含义的样式文件 | Add or update style files that do not affect the meaning of the code -->
|
|
||||||
- [ ] test <!-- 添加缺失的测试或纠正现有的测试 | Adding missing tests or correcting existing tests -->
|
|
||||||
- [ ] docs <!-- 仅文档更新 | Documentation only changes -->
|
|
||||||
- [ ] ci <!-- 修改持续集成配置文件和脚本 | Changes to our CI configuration files and scripts -->
|
|
||||||
- [ ] chore <!-- 其他不修改 src 或 test 文件的变更 | Other changes that don’t modify src or test files -->
|
|
||||||
- [ ] build <!-- 进行架构变更 | Make architectural changes -->
|
|
||||||
|
|
||||||
#### 🔀 变更说明 | Description of Change
|
|
||||||
|
|
||||||
<!--
|
|
||||||
感谢您的 Pull Request ,请提供此 Pull Request 的变更说明
|
|
||||||
Thank you for your Pull Request. Please provide a description above.
|
|
||||||
-->
|
|
||||||
|
|
||||||
#### 📝 补充信息 | Additional Information
|
|
||||||
|
|
||||||
<!--
|
|
||||||
请添加与此 Pull Request 相关的补充信息
|
|
||||||
Add any other context about the Pull Request here.
|
|
||||||
-->
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -43,6 +43,4 @@ dev
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
*.key
|
*.key
|
||||||
*.key.pub
|
*.key.pub
|
||||||
|
|
||||||
masks.json
|
|
||||||
@@ -43,7 +43,7 @@ COPY --from=builder /app/.next/server ./.next/server
|
|||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD if [ -n "$PROXY_URL" ]; then \
|
CMD if [ -n "$PROXY_URL" ]; then \
|
||||||
export HOSTNAME="0.0.0.0"; \
|
export HOSTNAME="127.0.0.1"; \
|
||||||
protocol=$(echo $PROXY_URL | cut -d: -f1); \
|
protocol=$(echo $PROXY_URL | cut -d: -f1); \
|
||||||
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
|
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
|
||||||
port=$(echo $PROXY_URL | cut -d: -f3); \
|
port=$(echo $PROXY_URL | cut -d: -f3); \
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023-2024 Zhang Yifei
|
Copyright (c) 2023 Zhang Yifei
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
158
README.md
158
README.md
@@ -1,8 +1,5 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
<img src="./docs/images/head-cover.png" alt="icon"/>
|
||||||
<a href='#企业版'>
|
|
||||||
<img src="./docs/images/ent.svg" alt="icon"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<h1 align="center">NextChat (ChatGPT Next Web)</h1>
|
<h1 align="center">NextChat (ChatGPT Next Web)</h1>
|
||||||
|
|
||||||
@@ -17,49 +14,27 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
|
|||||||
[![MacOS][MacOS-image]][download-url]
|
[![MacOS][MacOS-image]][download-url]
|
||||||
[![Linux][Linux-image]][download-url]
|
[![Linux][Linux-image]][download-url]
|
||||||
|
|
||||||
[Web App](https://app.nextchat.dev/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
|
[Web App](https://app.nextchat.dev/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Twitter](https://twitter.com/NextChatDev)
|
||||||
|
|
||||||
[网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
|
[网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
|
||||||
|
|
||||||
[web-url]: https://app.nextchat.dev/
|
[web-url]: https://chatgpt.nextweb.fun
|
||||||
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
|
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
|
||||||
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
|
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
|
||||||
[Windows-image]: https://img.shields.io/badge/-Windows-blue?logo=windows
|
[Windows-image]: https://img.shields.io/badge/-Windows-blue?logo=windows
|
||||||
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
|
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
|
||||||
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
|
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
|
||||||
|
|
||||||
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat)
|
||||||
|
|
||||||
|
[](https://zeabur.com/templates/ZBUEFA)
|
||||||
|
|
||||||
|
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Enterprise Edition
|
|
||||||
|
|
||||||
Meeting Your Company's Privatization and Customization Deployment Requirements:
|
|
||||||
- **Brand Customization**: Tailored VI/UI to seamlessly align with your corporate brand image.
|
|
||||||
- **Resource Integration**: Unified configuration and management of dozens of AI resources by company administrators, ready for use by team members.
|
|
||||||
- **Permission Control**: Clearly defined member permissions, resource permissions, and knowledge base permissions, all controlled via a corporate-grade Admin Panel.
|
|
||||||
- **Knowledge Integration**: Combining your internal knowledge base with AI capabilities, making it more relevant to your company's specific business needs compared to general AI.
|
|
||||||
- **Security Auditing**: Automatically intercept sensitive inquiries and trace all historical conversation records, ensuring AI adherence to corporate information security standards.
|
|
||||||
- **Private Deployment**: Enterprise-level private deployment supporting various mainstream private cloud solutions, ensuring data security and privacy protection.
|
|
||||||
- **Continuous Updates**: Ongoing updates and upgrades in cutting-edge capabilities like multimodal AI, ensuring consistent innovation and advancement.
|
|
||||||
|
|
||||||
For enterprise inquiries, please contact: **business@nextchat.dev**
|
|
||||||
|
|
||||||
## 企业版
|
|
||||||
|
|
||||||
满足企业用户私有化部署和个性化定制需求:
|
|
||||||
- **品牌定制**:企业量身定制 VI/UI,与企业品牌形象无缝契合
|
|
||||||
- **资源集成**:由企业管理人员统一配置和管理数十种 AI 资源,团队成员开箱即用
|
|
||||||
- **权限管理**:成员权限、资源权限、知识库权限层级分明,企业级 Admin Panel 统一控制
|
|
||||||
- **知识接入**:企业内部知识库与 AI 能力相结合,比通用 AI 更贴近企业自身业务需求
|
|
||||||
- **安全审计**:自动拦截敏感提问,支持追溯全部历史对话记录,让 AI 也能遵循企业信息安全规范
|
|
||||||
- **私有部署**:企业级私有部署,支持各类主流私有云部署,确保数据安全和隐私保护
|
|
||||||
- **持续更新**:提供多模态、智能体等前沿能力持续更新升级服务,常用常新、持续先进
|
|
||||||
|
|
||||||
企业版咨询: **business@nextchat.dev**
|
|
||||||
|
|
||||||
<img width="300" src="https://github.com/user-attachments/assets/3daeb7b6-ab63-4542-9141-2e4a12c80601">
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Deploy for free with one-click** on Vercel in under 1 minute
|
- **Deploy for free with one-click** on Vercel in under 1 minute
|
||||||
@@ -74,12 +49,6 @@ For enterprise inquiries, please contact: **business@nextchat.dev**
|
|||||||
- Automatically compresses chat history to support long conversations while also saving your tokens
|
- Automatically compresses chat history to support long conversations while also saving your tokens
|
||||||
- I18n: English, 简体中文, 繁体中文, 日本語, Français, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština, 한국어, Indonesia
|
- I18n: English, 简体中文, 繁体中文, 日本語, Français, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština, 한국어, Indonesia
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||||
@@ -88,15 +57,10 @@ For enterprise inquiries, please contact: **business@nextchat.dev**
|
|||||||
- [x] Share as image, share to ShareGPT [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
- [x] Share as image, share to ShareGPT [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||||
- [x] Desktop App with tauri
|
- [x] Desktop App with tauri
|
||||||
- [x] Self-host Model: Fully compatible with [RWKV-Runner](https://github.com/josStorer/RWKV-Runner), as well as server deployment of [LocalAI](https://github.com/go-skynet/LocalAI): llama/gpt4all/rwkv/vicuna/koala/gpt4all-j/cerebras/falcon/dolly etc.
|
- [x] Self-host Model: Fully compatible with [RWKV-Runner](https://github.com/josStorer/RWKV-Runner), as well as server deployment of [LocalAI](https://github.com/go-skynet/LocalAI): llama/gpt4all/rwkv/vicuna/koala/gpt4all-j/cerebras/falcon/dolly etc.
|
||||||
- [x] Artifacts: Easily preview, copy and share generated content/webpages through a separate window [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092)
|
- [ ] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
- [x] Plugins: support artifacts, network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
|
||||||
- [x] artifacts
|
|
||||||
- [ ] network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
|
||||||
- [ ] local knowledge base
|
|
||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- 🚀 v2.14.0 Now supports Artifacts & SD
|
|
||||||
- 🚀 v2.10.1 support Google Gemini Pro model.
|
- 🚀 v2.10.1 support Google Gemini Pro model.
|
||||||
- 🚀 v2.9.11 you can use azure endpoint now.
|
- 🚀 v2.9.11 you can use azure endpoint now.
|
||||||
- 🚀 v2.8 now we have a client that runs across all platforms!
|
- 🚀 v2.8 now we have a client that runs across all platforms!
|
||||||
@@ -125,21 +89,15 @@ For enterprise inquiries, please contact: **business@nextchat.dev**
|
|||||||
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||||
- [x] 使用 tauri 打包桌面应用
|
- [x] 使用 tauri 打包桌面应用
|
||||||
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等,或者使用 [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)
|
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等,或者使用 [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)
|
||||||
- [x] Artifacts: 通过独立窗口,轻松预览、复制和分享生成的内容/可交互网页 [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092)
|
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||||
- [x] 插件机制,支持 artifacts,联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
|
||||||
- [x] artifacts
|
|
||||||
- [ ] 支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
|
||||||
- [ ] 本地知识库
|
|
||||||
|
|
||||||
## 最新动态
|
## 最新动态
|
||||||
|
|
||||||
- 🚀 v2.14.0 现在支持 Artifacts & SD 了。
|
|
||||||
- 🚀 v2.10.1 现在支持 Gemini Pro 模型。
|
|
||||||
- 🚀 v2.9.11 现在可以使用自定义 Azure 服务了。
|
|
||||||
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
|
|
||||||
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
|
||||||
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
||||||
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
||||||
|
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
||||||
|
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
|
||||||
|
- 🚀 v2.9.11 现在可以使用自定义 Azure 服务了。
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
@@ -222,7 +180,7 @@ Specify OpenAI organization ID.
|
|||||||
|
|
||||||
### `AZURE_URL` (optional)
|
### `AZURE_URL` (optional)
|
||||||
|
|
||||||
> Example: https://{azure-resource-url}/openai
|
> Example: https://{azure-resource-url}/openai/deployments/{deploy-name}
|
||||||
|
|
||||||
Azure deploy url.
|
Azure deploy url.
|
||||||
|
|
||||||
@@ -242,58 +200,6 @@ Google Gemini Pro Api Key.
|
|||||||
|
|
||||||
Google Gemini Pro Api Url.
|
Google Gemini Pro Api Url.
|
||||||
|
|
||||||
### `ANTHROPIC_API_KEY` (optional)
|
|
||||||
|
|
||||||
anthropic claude Api Key.
|
|
||||||
|
|
||||||
### `ANTHROPIC_API_VERSION` (optional)
|
|
||||||
|
|
||||||
anthropic claude Api version.
|
|
||||||
|
|
||||||
### `ANTHROPIC_URL` (optional)
|
|
||||||
|
|
||||||
anthropic claude Api Url.
|
|
||||||
|
|
||||||
### `BAIDU_API_KEY` (optional)
|
|
||||||
|
|
||||||
Baidu Api Key.
|
|
||||||
|
|
||||||
### `BAIDU_SECRET_KEY` (optional)
|
|
||||||
|
|
||||||
Baidu Secret Key.
|
|
||||||
|
|
||||||
### `BAIDU_URL` (optional)
|
|
||||||
|
|
||||||
Baidu Api Url.
|
|
||||||
|
|
||||||
### `BYTEDANCE_API_KEY` (optional)
|
|
||||||
|
|
||||||
ByteDance Api Key.
|
|
||||||
|
|
||||||
### `BYTEDANCE_URL` (optional)
|
|
||||||
|
|
||||||
ByteDance Api Url.
|
|
||||||
|
|
||||||
### `ALIBABA_API_KEY` (optional)
|
|
||||||
|
|
||||||
Alibaba Cloud Api Key.
|
|
||||||
|
|
||||||
### `ALIBABA_URL` (optional)
|
|
||||||
|
|
||||||
Alibaba Cloud Api Url.
|
|
||||||
|
|
||||||
### `IFLYTEK_URL` (Optional)
|
|
||||||
|
|
||||||
iflytek Api Url.
|
|
||||||
|
|
||||||
### `IFLYTEK_API_KEY` (Optional)
|
|
||||||
|
|
||||||
iflytek Api Key.
|
|
||||||
|
|
||||||
### `IFLYTEK_API_SECRET` (Optional)
|
|
||||||
|
|
||||||
iflytek Api Secret.
|
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (optional)
|
### `HIDE_USER_API_KEY` (optional)
|
||||||
|
|
||||||
> Default: Empty
|
> Default: Empty
|
||||||
@@ -327,36 +233,6 @@ To control custom models, use `+` to add a custom model, use `-` to hide a model
|
|||||||
|
|
||||||
User `-all` to disable all default models, `+all` to enable all default models.
|
User `-all` to disable all default models, `+all` to enable all default models.
|
||||||
|
|
||||||
For Azure: use `modelName@azure=deploymentName` to customize model name and deployment name.
|
|
||||||
> Example: `+gpt-3.5-turbo@azure=gpt35` will show option `gpt35(Azure)` in model list.
|
|
||||||
> If you only can use Azure model, `-all,+gpt-3.5-turbo@azure=gpt35` will `gpt35(Azure)` the only option in model list.
|
|
||||||
|
|
||||||
For ByteDance: use `modelName@bytedance=deploymentName` to customize model name and deployment name.
|
|
||||||
> Example: `+Doubao-lite-4k@bytedance=ep-xxxxx-xxx` will show option `Doubao-lite-4k(ByteDance)` in model list.
|
|
||||||
|
|
||||||
### `DEFAULT_MODEL` (optional)
|
|
||||||
|
|
||||||
Change default model
|
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (optional)
|
|
||||||
|
|
||||||
You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format:
|
|
||||||
- Each address must be a complete endpoint
|
|
||||||
> `https://xxxx/yyy`
|
|
||||||
- Multiple addresses are connected by ', '
|
|
||||||
|
|
||||||
### `DEFAULT_INPUT_TEMPLATE` (optional)
|
|
||||||
|
|
||||||
Customize the default template used to initialize the User Input Preprocessing configuration item in Settings.
|
|
||||||
|
|
||||||
### `STABILITY_API_KEY` (optional)
|
|
||||||
|
|
||||||
Stability API key.
|
|
||||||
|
|
||||||
### `STABILITY_URL` (optional)
|
|
||||||
|
|
||||||
Customize Stability API url.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
NodeJS >= 18, Docker >= 20
|
NodeJS >= 18, Docker >= 20
|
||||||
|
|||||||
128
README_CN.md
128
README_CN.md
@@ -1,34 +1,22 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
<img src="./docs/images/icon.svg" alt="预览"/>
|
||||||
<a href='#企业版'>
|
|
||||||
<img src="./docs/images/ent.svg" alt="icon"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<h1 align="center">NextChat</h1>
|
<h1 align="center">NextChat</h1>
|
||||||
|
|
||||||
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
|
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
|
||||||
|
|
||||||
[企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) /[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
|
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
|
||||||
|
|
||||||
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
||||||
|
|
||||||
|
[](https://zeabur.com/templates/ZBUEFA)
|
||||||
|
|
||||||
|
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 企业版
|
|
||||||
|
|
||||||
满足您公司私有化部署和定制需求
|
|
||||||
- **品牌定制**:企业量身定制 VI/UI,与企业品牌形象无缝契合
|
|
||||||
- **资源集成**:由企业管理人员统一配置和管理数十种 AI 资源,团队成员开箱即用
|
|
||||||
- **权限管理**:成员权限、资源权限、知识库权限层级分明,企业级 Admin Panel 统一控制
|
|
||||||
- **知识接入**:企业内部知识库与 AI 能力相结合,比通用 AI 更贴近企业自身业务需求
|
|
||||||
- **安全审计**:自动拦截敏感提问,支持追溯全部历史对话记录,让 AI 也能遵循企业信息安全规范
|
|
||||||
- **私有部署**:企业级私有部署,支持各类主流私有云部署,确保数据安全和隐私保护
|
|
||||||
- **持续更新**:提供多模态、智能体等前沿能力持续更新升级服务,常用常新、持续先进
|
|
||||||
|
|
||||||
企业版咨询: **business@nextchat.dev**
|
|
||||||
|
|
||||||
<img width="300" src="https://github.com/user-attachments/assets/3daeb7b6-ab63-4542-9141-2e4a12c80601">
|
|
||||||
|
|
||||||
## 开始使用
|
## 开始使用
|
||||||
|
|
||||||
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
|
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
|
||||||
@@ -37,12 +25,6 @@
|
|||||||
3. 部署完毕后,即可开始使用;
|
3. 部署完毕后,即可开始使用;
|
||||||
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
|
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 保持更新
|
## 保持更新
|
||||||
|
|
||||||
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
|
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
|
||||||
@@ -112,7 +94,7 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
|
|||||||
|
|
||||||
### `AZURE_URL` (可选)
|
### `AZURE_URL` (可选)
|
||||||
|
|
||||||
> 形如:https://{azure-resource-url}/openai
|
> 形如:https://{azure-resource-url}/openai/deployments/{deploy-name}
|
||||||
|
|
||||||
Azure 部署地址。
|
Azure 部署地址。
|
||||||
|
|
||||||
@@ -124,68 +106,14 @@ Azure 密钥。
|
|||||||
|
|
||||||
Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。
|
Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。
|
||||||
|
|
||||||
### `GOOGLE_API_KEY` (可选)
|
### `GOOGLE_API_KEY` (optional)
|
||||||
|
|
||||||
Google Gemini Pro 密钥.
|
Google Gemini Pro 密钥.
|
||||||
|
|
||||||
### `GOOGLE_URL` (可选)
|
### `GOOGLE_URL` (optional)
|
||||||
|
|
||||||
Google Gemini Pro Api Url.
|
Google Gemini Pro Api Url.
|
||||||
|
|
||||||
### `ANTHROPIC_API_KEY` (可选)
|
|
||||||
|
|
||||||
anthropic claude Api Key.
|
|
||||||
|
|
||||||
### `ANTHROPIC_API_VERSION` (可选)
|
|
||||||
|
|
||||||
anthropic claude Api version.
|
|
||||||
|
|
||||||
### `ANTHROPIC_URL` (可选)
|
|
||||||
|
|
||||||
anthropic claude Api Url.
|
|
||||||
|
|
||||||
### `BAIDU_API_KEY` (可选)
|
|
||||||
|
|
||||||
Baidu Api Key.
|
|
||||||
|
|
||||||
### `BAIDU_SECRET_KEY` (可选)
|
|
||||||
|
|
||||||
Baidu Secret Key.
|
|
||||||
|
|
||||||
### `BAIDU_URL` (可选)
|
|
||||||
|
|
||||||
Baidu Api Url.
|
|
||||||
|
|
||||||
### `BYTEDANCE_API_KEY` (可选)
|
|
||||||
|
|
||||||
ByteDance Api Key.
|
|
||||||
|
|
||||||
### `BYTEDANCE_URL` (可选)
|
|
||||||
|
|
||||||
ByteDance Api Url.
|
|
||||||
|
|
||||||
### `ALIBABA_API_KEY` (可选)
|
|
||||||
|
|
||||||
阿里云(千问)Api Key.
|
|
||||||
|
|
||||||
### `ALIBABA_URL` (可选)
|
|
||||||
|
|
||||||
阿里云(千问)Api Url.
|
|
||||||
|
|
||||||
### `IFLYTEK_URL` (可选)
|
|
||||||
|
|
||||||
讯飞星火Api Url.
|
|
||||||
|
|
||||||
### `IFLYTEK_API_KEY` (可选)
|
|
||||||
|
|
||||||
讯飞星火Api Key.
|
|
||||||
|
|
||||||
### `IFLYTEK_API_SECRET` (可选)
|
|
||||||
|
|
||||||
讯飞星火Api Secret.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (可选)
|
### `HIDE_USER_API_KEY` (可选)
|
||||||
|
|
||||||
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
|
||||||
@@ -202,13 +130,6 @@ ByteDance Api Url.
|
|||||||
|
|
||||||
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (可选)
|
|
||||||
|
|
||||||
如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求:
|
|
||||||
- 每一个地址必须是一个完整的 endpoint
|
|
||||||
> `https://xxxx/xxx`
|
|
||||||
- 多个地址以`,`相连
|
|
||||||
|
|
||||||
### `CUSTOM_MODELS` (可选)
|
### `CUSTOM_MODELS` (可选)
|
||||||
|
|
||||||
> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。
|
> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。
|
||||||
@@ -216,31 +137,6 @@ ByteDance Api Url.
|
|||||||
|
|
||||||
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
|
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
|
||||||
|
|
||||||
在Azure的模式下,支持使用`modelName@azure=deploymentName`的方式配置模型名称和部署名称(deploy-name)
|
|
||||||
> 示例:`+gpt-3.5-turbo@azure=gpt35`这个配置会在模型列表显示一个`gpt35(Azure)`的选项。
|
|
||||||
> 如果你只能使用Azure模式,那么设置 `-all,+gpt-3.5-turbo@azure=gpt35` 则可以让对话的默认使用 `gpt35(Azure)`
|
|
||||||
|
|
||||||
在ByteDance的模式下,支持使用`modelName@bytedance=deploymentName`的方式配置模型名称和部署名称(deploy-name)
|
|
||||||
> 示例: `+Doubao-lite-4k@bytedance=ep-xxxxx-xxx`这个配置会在模型列表显示一个`Doubao-lite-4k(ByteDance)`的选项
|
|
||||||
|
|
||||||
|
|
||||||
### `DEFAULT_MODEL` (可选)
|
|
||||||
|
|
||||||
更改默认模型
|
|
||||||
|
|
||||||
### `DEFAULT_INPUT_TEMPLATE` (可选)
|
|
||||||
|
|
||||||
自定义默认的 template,用于初始化『设置』中的『用户输入预处理』配置项
|
|
||||||
|
|
||||||
### `STABILITY_API_KEY` (optional)
|
|
||||||
|
|
||||||
Stability API密钥
|
|
||||||
|
|
||||||
### `STABILITY_URL` (optional)
|
|
||||||
|
|
||||||
自定义的Stability API请求地址
|
|
||||||
|
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
点击下方按钮,开始二次开发:
|
点击下方按钮,开始二次开发:
|
||||||
|
|||||||
310
README_JA.md
310
README_JA.md
@@ -1,310 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<img src="./docs/images/ent.svg" alt="プレビュー"/>
|
|
||||||
|
|
||||||
<h1 align="center">NextChat</h1>
|
|
||||||
|
|
||||||
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
|
|
||||||
|
|
||||||
[企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
|
|
||||||
|
|
||||||
[<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 企業版
|
|
||||||
|
|
||||||
あなたの会社のプライベートデプロイとカスタマイズのニーズに応える
|
|
||||||
- **ブランドカスタマイズ**:企業向けに特別に設計された VI/UI、企業ブランドイメージとシームレスにマッチ
|
|
||||||
- **リソース統合**:企業管理者が数十種類のAIリソースを統一管理、チームメンバーはすぐに使用可能
|
|
||||||
- **権限管理**:メンバーの権限、リソースの権限、ナレッジベースの権限を明確にし、企業レベルのAdmin Panelで統一管理
|
|
||||||
- **知識の統合**:企業内部のナレッジベースとAI機能を結びつけ、汎用AIよりも企業自身の業務ニーズに近づける
|
|
||||||
- **セキュリティ監査**:機密質問を自動的にブロックし、すべての履歴対話を追跡可能にし、AIも企業の情報セキュリティ基準に従わせる
|
|
||||||
- **プライベートデプロイ**:企業レベルのプライベートデプロイ、主要なプライベートクラウドデプロイをサポートし、データのセキュリティとプライバシーを保護
|
|
||||||
- **継続的な更新**:マルチモーダル、エージェントなどの最先端機能を継続的に更新し、常に最新であり続ける
|
|
||||||
|
|
||||||
企業版のお問い合わせ: **business@nextchat.dev**
|
|
||||||
|
|
||||||
|
|
||||||
## 始めに
|
|
||||||
|
|
||||||
1. [OpenAI API Key](https://platform.openai.com/account/api-keys)を準備する;
|
|
||||||
2. 右側のボタンをクリックしてデプロイを開始:
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&env=GOOGLE_API_KEY&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web) 、GitHubアカウントで直接ログインし、環境変数ページにAPI Keyと[ページアクセスパスワード](#設定ページアクセスパスワード) CODEを入力してください;
|
|
||||||
3. デプロイが完了したら、すぐに使用を開始できます;
|
|
||||||
4. (オプション)[カスタムドメインをバインド](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercelが割り当てたドメインDNSは一部の地域で汚染されているため、カスタムドメインをバインドすると直接接続できます。
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
## 更新を維持する
|
|
||||||
|
|
||||||
もし上記の手順に従ってワンクリックでプロジェクトをデプロイした場合、「更新があります」というメッセージが常に表示されることがあります。これは、Vercel がデフォルトで新しいプロジェクトを作成するためで、本プロジェクトを fork していないことが原因です。そのため、正しく更新を検出できません。
|
|
||||||
|
|
||||||
以下の手順に従って再デプロイすることをお勧めします:
|
|
||||||
|
|
||||||
- 元のリポジトリを削除する
|
|
||||||
- ページ右上の fork ボタンを使って、本プロジェクトを fork する
|
|
||||||
- Vercel で再度選択してデプロイする、[詳細な手順はこちらを参照してください](./docs/vercel-ja.md)。
|
|
||||||
|
|
||||||
|
|
||||||
### 自動更新を開く
|
|
||||||
|
|
||||||
> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
|
|
||||||
|
|
||||||
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### 手動でコードを更新する
|
|
||||||
|
|
||||||
手動で即座に更新したい場合は、[GitHub のドキュメント](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)を参照して、fork したプロジェクトを上流のコードと同期する方法を確認してください。
|
|
||||||
|
|
||||||
このプロジェクトをスターまたはウォッチしたり、作者をフォローすることで、新機能の更新通知をすぐに受け取ることができます。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ページアクセスパスワードを設定する
|
|
||||||
|
|
||||||
> パスワードを設定すると、ユーザーは設定ページでアクセスコードを手動で入力しない限り、通常のチャットができず、未承認の状態であることを示すメッセージが表示されます。
|
|
||||||
|
|
||||||
> **警告**:パスワードの桁数は十分に長く設定してください。7桁以上が望ましいです。さもないと、[ブルートフォース攻撃を受ける可能性があります](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518)。
|
|
||||||
|
|
||||||
このプロジェクトは限られた権限管理機能を提供しています。Vercel プロジェクトのコントロールパネルで、環境変数ページに `CODE` という名前の環境変数を追加し、値をカンマで区切ったカスタムパスワードに設定してください:
|
|
||||||
|
|
||||||
```
|
|
||||||
code1,code2,code3
|
|
||||||
```
|
|
||||||
|
|
||||||
この環境変数を追加または変更した後、**プロジェクトを再デプロイ**して変更を有効にしてください。
|
|
||||||
|
|
||||||
|
|
||||||
## 環境変数
|
|
||||||
|
|
||||||
> 本プロジェクトのほとんどの設定は環境変数で行います。チュートリアル:[Vercel の環境変数を変更する方法](./docs/vercel-ja.md)。
|
|
||||||
|
|
||||||
### `OPENAI_API_KEY` (必須)
|
|
||||||
|
|
||||||
OpenAI の API キー。OpenAI アカウントページで申請したキーをカンマで区切って複数設定できます。これにより、ランダムにキーが選択されます。
|
|
||||||
|
|
||||||
### `CODE` (オプション)
|
|
||||||
|
|
||||||
アクセスパスワード。カンマで区切って複数設定可能。
|
|
||||||
|
|
||||||
**警告**:この項目を設定しないと、誰でもデプロイしたウェブサイトを利用でき、トークンが急速に消耗する可能性があるため、設定をお勧めします。
|
|
||||||
|
|
||||||
### `BASE_URL` (オプション)
|
|
||||||
|
|
||||||
> デフォルト: `https://api.openai.com`
|
|
||||||
|
|
||||||
> 例: `http://your-openai-proxy.com`
|
|
||||||
|
|
||||||
OpenAI API のプロキシ URL。手動で OpenAI API のプロキシを設定している場合はこのオプションを設定してください。
|
|
||||||
|
|
||||||
> SSL 証明書の問題がある場合は、`BASE_URL` のプロトコルを http に設定してください。
|
|
||||||
|
|
||||||
### `OPENAI_ORG_ID` (オプション)
|
|
||||||
|
|
||||||
OpenAI の組織 ID を指定します。
|
|
||||||
|
|
||||||
### `AZURE_URL` (オプション)
|
|
||||||
|
|
||||||
> 形式: https://{azure-resource-url}/openai/deployments/{deploy-name}
|
|
||||||
> `CUSTOM_MODELS` で `displayName` 形式で {deploy-name} を設定した場合、`AZURE_URL` から {deploy-name} を省略できます。
|
|
||||||
|
|
||||||
Azure のデプロイ URL。
|
|
||||||
|
|
||||||
### `AZURE_API_KEY` (オプション)
|
|
||||||
|
|
||||||
Azure の API キー。
|
|
||||||
|
|
||||||
### `AZURE_API_VERSION` (オプション)
|
|
||||||
|
|
||||||
Azure API バージョン。[Azure ドキュメント](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)で確認できます。
|
|
||||||
|
|
||||||
### `GOOGLE_API_KEY` (オプション)
|
|
||||||
|
|
||||||
Google Gemini Pro API キー。
|
|
||||||
|
|
||||||
### `GOOGLE_URL` (オプション)
|
|
||||||
|
|
||||||
Google Gemini Pro API の URL。
|
|
||||||
|
|
||||||
### `ANTHROPIC_API_KEY` (オプション)
|
|
||||||
|
|
||||||
Anthropic Claude API キー。
|
|
||||||
|
|
||||||
### `ANTHROPIC_API_VERSION` (オプション)
|
|
||||||
|
|
||||||
Anthropic Claude API バージョン。
|
|
||||||
|
|
||||||
### `ANTHROPIC_URL` (オプション)
|
|
||||||
|
|
||||||
Anthropic Claude API の URL。
|
|
||||||
|
|
||||||
### `BAIDU_API_KEY` (オプション)
|
|
||||||
|
|
||||||
Baidu API キー。
|
|
||||||
|
|
||||||
### `BAIDU_SECRET_KEY` (オプション)
|
|
||||||
|
|
||||||
Baidu シークレットキー。
|
|
||||||
|
|
||||||
### `BAIDU_URL` (オプション)
|
|
||||||
|
|
||||||
Baidu API の URL。
|
|
||||||
|
|
||||||
### `BYTEDANCE_API_KEY` (オプション)
|
|
||||||
|
|
||||||
ByteDance API キー。
|
|
||||||
|
|
||||||
### `BYTEDANCE_URL` (オプション)
|
|
||||||
|
|
||||||
ByteDance API の URL。
|
|
||||||
|
|
||||||
### `ALIBABA_API_KEY` (オプション)
|
|
||||||
|
|
||||||
アリババ(千问)API キー。
|
|
||||||
|
|
||||||
### `ALIBABA_URL` (オプション)
|
|
||||||
|
|
||||||
アリババ(千问)API の URL。
|
|
||||||
|
|
||||||
### `HIDE_USER_API_KEY` (オプション)
|
|
||||||
|
|
||||||
ユーザーが API キーを入力できないようにしたい場合は、この環境変数を 1 に設定します。
|
|
||||||
|
|
||||||
### `DISABLE_GPT4` (オプション)
|
|
||||||
|
|
||||||
ユーザーが GPT-4 を使用できないようにしたい場合は、この環境変数を 1 に設定します。
|
|
||||||
|
|
||||||
### `ENABLE_BALANCE_QUERY` (オプション)
|
|
||||||
|
|
||||||
バランスクエリ機能を有効にしたい場合は、この環境変数を 1 に設定します。
|
|
||||||
|
|
||||||
### `DISABLE_FAST_LINK` (オプション)
|
|
||||||
|
|
||||||
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
|
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (オプション)
|
|
||||||
|
|
||||||
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
|
|
||||||
- 各アドレスは完全なエンドポイントでなければなりません。
|
|
||||||
> `https://xxxx/xxx`
|
|
||||||
- 複数のアドレスは `,` で接続します。
|
|
||||||
|
|
||||||
### `CUSTOM_MODELS` (オプション)
|
|
||||||
|
|
||||||
> 例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` は `qwen-7b-chat` と `glm-6b` をモデルリストに追加し、`gpt-3.5-turbo` を削除し、`gpt-4-1106-preview` のモデル名を `gpt-4-turbo` として表示します。
|
|
||||||
> すべてのモデルを無効にし、特定のモデルを有効にしたい場合は、`-all,+gpt-3.5-turbo` を使用します。これは `gpt-3.5-turbo` のみを有効にすることを意味します。
|
|
||||||
|
|
||||||
モデルリストを管理します。`+` でモデルを追加し、`-` でモデルを非表示にし、`モデル名=表示名` でモデルの表示名をカスタマイズし、カンマで区切ります。
|
|
||||||
|
|
||||||
Azure モードでは、`modelName@azure=deploymentName` 形式でモデル名とデプロイ名(deploy-name)を設定できます。
|
|
||||||
> 例:`+gpt-3.5-turbo@azure=gpt35` この設定でモデルリストに `gpt35(Azure)` のオプションが表示されます。
|
|
||||||
|
|
||||||
ByteDance モードでは、`modelName@bytedance=deploymentName` 形式でモデル名とデプロイ名(deploy-name)を設定できます。
|
|
||||||
> 例: `+Doubao-lite-4k@bytedance=ep-xxxxx-xxx` この設定でモデルリストに `Doubao-lite-4k(ByteDance)` のオプションが表示されます。
|
|
||||||
|
|
||||||
### `DEFAULT_MODEL` (オプション)
|
|
||||||
|
|
||||||
デフォルトのモデルを変更します。
|
|
||||||
|
|
||||||
### `DEFAULT_INPUT_TEMPLATE` (オプション)
|
|
||||||
|
|
||||||
『設定』の『ユーザー入力前処理』の初期設定に使用するテンプレートをカスタマイズします。
|
|
||||||
|
|
||||||
|
|
||||||
## 開発
|
|
||||||
|
|
||||||
下のボタンをクリックして二次開発を開始してください:
|
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
|
||||||
|
|
||||||
コードを書く前に、プロジェクトのルートディレクトリに `.env.local` ファイルを新規作成し、環境変数を記入します:
|
|
||||||
|
|
||||||
```
|
|
||||||
OPENAI_API_KEY=<your api key here>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### ローカル開発
|
|
||||||
|
|
||||||
1. Node.js 18 と Yarn をインストールします。具体的な方法は ChatGPT にお尋ねください。
|
|
||||||
2. `yarn install && yarn dev` を実行します。⚠️ 注意:このコマンドはローカル開発用であり、デプロイには使用しないでください。
|
|
||||||
3. ローカルでデプロイしたい場合は、`yarn install && yarn build && yarn start` コマンドを使用してください。プロセスを守るために pm2 を使用することもできます。詳細は ChatGPT にお尋ねください。
|
|
||||||
|
|
||||||
|
|
||||||
## デプロイ
|
|
||||||
|
|
||||||
### コンテナデプロイ(推奨)
|
|
||||||
|
|
||||||
> Docker バージョンは 20 以上が必要です。それ以下だとイメージが見つからないというエラーが出ます。
|
|
||||||
|
|
||||||
> ⚠️ 注意:Docker バージョンは最新バージョンより 1~2 日遅れることが多いため、デプロイ後に「更新があります」の通知が出続けることがありますが、正常です。
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker pull yidadaa/chatgpt-next-web
|
|
||||||
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=ページアクセスパスワード \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
プロキシを指定することもできます:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -d -p 3000:3000 \
|
|
||||||
-e OPENAI_API_KEY=sk-xxxx \
|
|
||||||
-e CODE=ページアクセスパスワード \
|
|
||||||
--net=host \
|
|
||||||
-e PROXY_URL=http://127.0.0.1:7890 \
|
|
||||||
yidadaa/chatgpt-next-web
|
|
||||||
```
|
|
||||||
|
|
||||||
ローカルプロキシがアカウントとパスワードを必要とする場合は、以下を使用できます:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
-e PROXY_URL="http://127.0.0.1:7890 user password"
|
|
||||||
```
|
|
||||||
|
|
||||||
他の環境変数を指定する必要がある場合は、上記のコマンドに `-e 環境変数=環境変数値` を追加して指定してください。
|
|
||||||
|
|
||||||
|
|
||||||
### ローカルデプロイ
|
|
||||||
|
|
||||||
コンソールで以下のコマンドを実行します:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ 注意:インストール中に問題が発生した場合は、Docker を使用してデプロイしてください。
|
|
||||||
|
|
||||||
|
|
||||||
## 謝辞
|
|
||||||
|
|
||||||
### 寄付者
|
|
||||||
|
|
||||||
> 英語版をご覧ください。
|
|
||||||
|
|
||||||
### 貢献者
|
|
||||||
|
|
||||||
[プロジェクトの貢献者リストはこちら](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
|
||||||
|
|
||||||
### 関連プロジェクト
|
|
||||||
|
|
||||||
- [one-api](https://github.com/songquanpeng/one-api): 一つのプラットフォームで大規模モデルのクォータ管理を提供し、市場に出回っているすべての主要な大規模言語モデルをサポートします。
|
|
||||||
|
|
||||||
|
|
||||||
## オープンソースライセンス
|
|
||||||
|
|
||||||
[MIT](https://opensource.org/license/mit/)
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { ApiPath } from "@/app/constant";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { handle as openaiHandler } from "../../openai";
|
|
||||||
import { handle as azureHandler } from "../../azure";
|
|
||||||
import { handle as googleHandler } from "../../google";
|
|
||||||
import { handle as anthropicHandler } from "../../anthropic";
|
|
||||||
import { handle as baiduHandler } from "../../baidu";
|
|
||||||
import { handle as bytedanceHandler } from "../../bytedance";
|
|
||||||
import { handle as alibabaHandler } from "../../alibaba";
|
|
||||||
import { handle as moonshotHandler } from "../../moonshot";
|
|
||||||
import { handle as stabilityHandler } from "../../stability";
|
|
||||||
import { handle as iflytekHandler } from "../../iflytek";
|
|
||||||
async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { provider: string; path: string[] } },
|
|
||||||
) {
|
|
||||||
const apiPath = `/api/${params.provider}`;
|
|
||||||
console.log(`[${params.provider} Route] params `, params);
|
|
||||||
switch (apiPath) {
|
|
||||||
case ApiPath.Azure:
|
|
||||||
return azureHandler(req, { params });
|
|
||||||
case ApiPath.Google:
|
|
||||||
return googleHandler(req, { params });
|
|
||||||
case ApiPath.Anthropic:
|
|
||||||
return anthropicHandler(req, { params });
|
|
||||||
case ApiPath.Baidu:
|
|
||||||
return baiduHandler(req, { params });
|
|
||||||
case ApiPath.ByteDance:
|
|
||||||
return bytedanceHandler(req, { params });
|
|
||||||
case ApiPath.Alibaba:
|
|
||||||
return alibabaHandler(req, { params });
|
|
||||||
// case ApiPath.Tencent: using "/api/tencent"
|
|
||||||
case ApiPath.Moonshot:
|
|
||||||
return moonshotHandler(req, { params });
|
|
||||||
case ApiPath.Stability:
|
|
||||||
return stabilityHandler(req, { params });
|
|
||||||
case ApiPath.Iflytek:
|
|
||||||
return iflytekHandler(req, { params });
|
|
||||||
default:
|
|
||||||
return openaiHandler(req, { params });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET = handle;
|
|
||||||
export const POST = handle;
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
||||||
export const preferredRegion = [
|
|
||||||
"arn1",
|
|
||||||
"bom1",
|
|
||||||
"cdg1",
|
|
||||||
"cle1",
|
|
||||||
"cpt1",
|
|
||||||
"dub1",
|
|
||||||
"fra1",
|
|
||||||
"gru1",
|
|
||||||
"hnd1",
|
|
||||||
"iad1",
|
|
||||||
"icn1",
|
|
||||||
"kix1",
|
|
||||||
"lhr1",
|
|
||||||
"pdx1",
|
|
||||||
"sfo1",
|
|
||||||
"sin1",
|
|
||||||
"syd1",
|
|
||||||
];
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
Alibaba,
|
|
||||||
ALIBABA_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Alibaba Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Qwen);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Alibaba] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
// alibaba use base url or just remove the path
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Alibaba, "");
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.alibabaUrl || ALIBABA_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}${path}`;
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: req.headers.get("Authorization") ?? "",
|
|
||||||
"X-DashScope-SSE": req.headers.get("X-DashScope-SSE") ?? "disable",
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
// #1815 try to refuse some request to some models
|
|
||||||
if (serverConfig.customModels && req.body) {
|
|
||||||
try {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
fetchOptions.body = clonedBody;
|
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
|
||||||
|
|
||||||
// not undefined and is false
|
|
||||||
if (
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Alibaba as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 403,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Alibaba] filter`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,18 +4,16 @@ import {
|
|||||||
Anthropic,
|
Anthropic,
|
||||||
ApiPath,
|
ApiPath,
|
||||||
DEFAULT_MODELS,
|
DEFAULT_MODELS,
|
||||||
ServiceProvider,
|
|
||||||
ModelProvider,
|
ModelProvider,
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "./auth";
|
import { auth } from "../../auth";
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
import { collectModelTable } from "@/app/utils/model";
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
|
||||||
|
|
||||||
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
|
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
|
||||||
|
|
||||||
export async function handle(
|
async function handle(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: { path: string[] } },
|
{ params }: { params: { path: string[] } },
|
||||||
) {
|
) {
|
||||||
@@ -56,6 +54,30 @@ export async function handle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
export const preferredRegion = [
|
||||||
|
"arn1",
|
||||||
|
"bom1",
|
||||||
|
"cdg1",
|
||||||
|
"cle1",
|
||||||
|
"cpt1",
|
||||||
|
"dub1",
|
||||||
|
"fra1",
|
||||||
|
"gru1",
|
||||||
|
"hnd1",
|
||||||
|
"iad1",
|
||||||
|
"icn1",
|
||||||
|
"kix1",
|
||||||
|
"lhr1",
|
||||||
|
"pdx1",
|
||||||
|
"sfo1",
|
||||||
|
"sin1",
|
||||||
|
"syd1",
|
||||||
|
];
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
async function request(req: NextRequest) {
|
||||||
@@ -91,8 +113,7 @@ async function request(req: NextRequest) {
|
|||||||
10 * 60 * 1000,
|
10 * 60 * 1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
// try rebuild url, when using cloudflare ai gateway in server
|
const fetchUrl = `${baseUrl}${path}`;
|
||||||
const fetchUrl = cloudflareAIGatewayUrl(`${baseUrl}${path}`);
|
|
||||||
|
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -115,19 +136,17 @@ async function request(req: NextRequest) {
|
|||||||
// #1815 try to refuse some request to some models
|
// #1815 try to refuse some request to some models
|
||||||
if (serverConfig.customModels && req.body) {
|
if (serverConfig.customModels && req.body) {
|
||||||
try {
|
try {
|
||||||
|
const modelTable = collectModelTable(
|
||||||
|
DEFAULT_MODELS,
|
||||||
|
serverConfig.customModels,
|
||||||
|
);
|
||||||
const clonedBody = await req.text();
|
const clonedBody = await req.text();
|
||||||
fetchOptions.body = clonedBody;
|
fetchOptions.body = clonedBody;
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
||||||
|
|
||||||
// not undefined and is false
|
// not undefined and is false
|
||||||
if (
|
if (modelTable[jsonBody?.model ?? ""].available === false) {
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Anthropic as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -142,17 +161,17 @@ async function request(req: NextRequest) {
|
|||||||
console.error(`[Anthropic] filter`, e);
|
console.error(`[Anthropic] filter`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log("[Anthropic request]", fetchOptions.headers, req.method);
|
console.log("[Anthropic request]", fetchOptions.headers, req.method);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
const res = await fetch(fetchUrl, fetchOptions);
|
||||||
|
|
||||||
// console.log(
|
console.log(
|
||||||
// "[Anthropic response]",
|
"[Anthropic response]",
|
||||||
// res.status,
|
res.status,
|
||||||
// " ",
|
" ",
|
||||||
// res.headers,
|
res.headers,
|
||||||
// res.url,
|
res.url,
|
||||||
// );
|
);
|
||||||
// to prevent browser prompt for credentials
|
// to prevent browser prompt for credentials
|
||||||
const newHeaders = new Headers(res.headers);
|
const newHeaders = new Headers(res.headers);
|
||||||
newHeaders.delete("www-authenticate");
|
newHeaders.delete("www-authenticate");
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import md5 from "spark-md5";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
|
|
||||||
async function handle(req: NextRequest, res: NextResponse) {
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
const storeUrl = () =>
|
|
||||||
`https://api.cloudflare.com/client/v4/accounts/${serverConfig.cloudflareAccountId}/storage/kv/namespaces/${serverConfig.cloudflareKVNamespaceId}`;
|
|
||||||
const storeHeaders = () => ({
|
|
||||||
Authorization: `Bearer ${serverConfig.cloudflareKVApiKey}`,
|
|
||||||
});
|
|
||||||
if (req.method === "POST") {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
const hashedCode = md5.hash(clonedBody).trim();
|
|
||||||
const body: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
expiration_ttl?: number;
|
|
||||||
} = {
|
|
||||||
key: hashedCode,
|
|
||||||
value: clonedBody,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const ttl = parseInt(serverConfig.cloudflareKVTTL as string);
|
|
||||||
if (ttl > 60) {
|
|
||||||
body["expiration_ttl"] = ttl;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
const res = await fetch(`${storeUrl()}/bulk`, {
|
|
||||||
headers: {
|
|
||||||
...storeHeaders(),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify([body]),
|
|
||||||
});
|
|
||||||
const result = await res.json();
|
|
||||||
console.log("save data", result);
|
|
||||||
if (result?.success) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ code: 0, id: hashedCode, result },
|
|
||||||
{ status: res.status },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: true, msg: "Save data error" },
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (req.method === "GET") {
|
|
||||||
const id = req?.nextUrl?.searchParams?.get("id");
|
|
||||||
const res = await fetch(`${storeUrl()}/values/${id}`, {
|
|
||||||
headers: storeHeaders(),
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: res.headers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: true, msg: "Invalid request" },
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const POST = handle;
|
|
||||||
export const GET = handle;
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
||||||
@@ -67,34 +67,15 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
|
|||||||
let systemApiKey: string | undefined;
|
let systemApiKey: string | undefined;
|
||||||
|
|
||||||
switch (modelProvider) {
|
switch (modelProvider) {
|
||||||
case ModelProvider.Stability:
|
|
||||||
systemApiKey = serverConfig.stabilityApiKey;
|
|
||||||
break;
|
|
||||||
case ModelProvider.GeminiPro:
|
case ModelProvider.GeminiPro:
|
||||||
systemApiKey = serverConfig.googleApiKey;
|
systemApiKey = serverConfig.googleApiKey;
|
||||||
break;
|
break;
|
||||||
case ModelProvider.Claude:
|
case ModelProvider.Claude:
|
||||||
systemApiKey = serverConfig.anthropicApiKey;
|
systemApiKey = serverConfig.anthropicApiKey;
|
||||||
break;
|
break;
|
||||||
case ModelProvider.Doubao:
|
|
||||||
systemApiKey = serverConfig.bytedanceApiKey;
|
|
||||||
break;
|
|
||||||
case ModelProvider.Ernie:
|
|
||||||
systemApiKey = serverConfig.baiduApiKey;
|
|
||||||
break;
|
|
||||||
case ModelProvider.Qwen:
|
|
||||||
systemApiKey = serverConfig.alibabaApiKey;
|
|
||||||
break;
|
|
||||||
case ModelProvider.Moonshot:
|
|
||||||
systemApiKey = serverConfig.moonshotApiKey;
|
|
||||||
break;
|
|
||||||
case ModelProvider.Iflytek:
|
|
||||||
systemApiKey =
|
|
||||||
serverConfig.iflytekApiKey + ":" + serverConfig.iflytekApiSecret;
|
|
||||||
break;
|
|
||||||
case ModelProvider.GPT:
|
case ModelProvider.GPT:
|
||||||
default:
|
default:
|
||||||
if (req.nextUrl.pathname.includes("azure/deployments")) {
|
if (serverConfig.isAzure) {
|
||||||
systemApiKey = serverConfig.azureApiKey;
|
systemApiKey = serverConfig.azureApiKey;
|
||||||
} else {
|
} else {
|
||||||
systemApiKey = serverConfig.apiKey;
|
systemApiKey = serverConfig.apiKey;
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import { ModelProvider } from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "./auth";
|
|
||||||
import { requestOpenai } from "./common";
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Azure Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const subpath = params.path.join("/");
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.GPT);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await requestOpenai(req);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Azure] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
145
app/api/baidu.ts
145
app/api/baidu.ts
@@ -1,145 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
BAIDU_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
BAIDU_OATUH_URL,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import { getAccessToken } from "@/app/utils/baidu";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Baidu Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Ernie);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serverConfig.baiduApiKey || !serverConfig.baiduSecretKey) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `missing BAIDU_API_KEY or BAIDU_SECRET_KEY in server env vars`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Baidu] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Baidu, "");
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.baiduUrl || BAIDU_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { access_token } = await getAccessToken(
|
|
||||||
serverConfig.baiduApiKey as string,
|
|
||||||
serverConfig.baiduSecretKey as string,
|
|
||||||
);
|
|
||||||
const fetchUrl = `${baseUrl}${path}?access_token=${access_token}`;
|
|
||||||
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
// #1815 try to refuse some request to some models
|
|
||||||
if (serverConfig.customModels && req.body) {
|
|
||||||
try {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
fetchOptions.body = clonedBody;
|
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
|
||||||
|
|
||||||
// not undefined and is false
|
|
||||||
if (
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Baidu as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 403,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Baidu] filter`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
BYTEDANCE_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[ByteDance Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Doubao);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[ByteDance] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.ByteDance, "");
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.bytedanceUrl || BYTEDANCE_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}${path}`;
|
|
||||||
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: req.headers.get("Authorization") ?? "",
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
// #1815 try to refuse some request to some models
|
|
||||||
if (serverConfig.customModels && req.body) {
|
|
||||||
try {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
fetchOptions.body = clonedBody;
|
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
|
||||||
|
|
||||||
// not undefined and is false
|
|
||||||
if (
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.ByteDance as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 403,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[ByteDance] filter`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getServerSideConfig } from "../config/server";
|
import { getServerSideConfig } from "../config/server";
|
||||||
import {
|
import { DEFAULT_MODELS, OPENAI_BASE_URL, GEMINI_BASE_URL } from "../constant";
|
||||||
DEFAULT_MODELS,
|
import { collectModelTable } from "../utils/model";
|
||||||
OPENAI_BASE_URL,
|
import { makeAzurePath } from "../azure";
|
||||||
GEMINI_BASE_URL,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "../constant";
|
|
||||||
import { isModelAvailableInServer } from "../utils/model";
|
|
||||||
import { cloudflareAIGatewayUrl } from "../utils/cloudflare";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
export async function requestOpenai(req: NextRequest) {
|
export async function requestOpenai(req: NextRequest) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
const isAzure = req.nextUrl.pathname.includes("azure/deployments");
|
|
||||||
|
|
||||||
var authValue,
|
var authValue,
|
||||||
authHeaderName = "";
|
authHeaderName = "";
|
||||||
if (isAzure) {
|
if (serverConfig.isAzure) {
|
||||||
authValue =
|
authValue =
|
||||||
req.headers
|
req.headers
|
||||||
.get("Authorization")
|
.get("Authorization")
|
||||||
@@ -38,7 +31,7 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let baseUrl =
|
let baseUrl =
|
||||||
(isAzure ? serverConfig.azureUrl : serverConfig.baseUrl) || OPENAI_BASE_URL;
|
serverConfig.azureUrl || serverConfig.baseUrl || OPENAI_BASE_URL;
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
if (!baseUrl.startsWith("http")) {
|
||||||
baseUrl = `https://${baseUrl}`;
|
baseUrl = `https://${baseUrl}`;
|
||||||
@@ -58,46 +51,17 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
10 * 60 * 1000,
|
10 * 60 * 1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isAzure) {
|
if (serverConfig.isAzure) {
|
||||||
const azureApiVersion =
|
if (!serverConfig.azureApiVersion) {
|
||||||
req?.nextUrl?.searchParams?.get("api-version") ||
|
return NextResponse.json({
|
||||||
serverConfig.azureApiVersion;
|
error: true,
|
||||||
baseUrl = baseUrl.split("/deployments").shift() as string;
|
message: `missing AZURE_API_VERSION in server env vars`,
|
||||||
path = `${req.nextUrl.pathname.replaceAll(
|
});
|
||||||
"/api/azure/",
|
|
||||||
"",
|
|
||||||
)}?api-version=${azureApiVersion}`;
|
|
||||||
|
|
||||||
// Forward compatibility:
|
|
||||||
// if display_name(deployment_name) not set, and '{deploy-id}' in AZURE_URL
|
|
||||||
// then using default '{deploy-id}'
|
|
||||||
if (serverConfig.customModels && serverConfig.azureUrl) {
|
|
||||||
const modelName = path.split("/")[1];
|
|
||||||
let realDeployName = "";
|
|
||||||
serverConfig.customModels
|
|
||||||
.split(",")
|
|
||||||
.filter((v) => !!v && !v.startsWith("-") && v.includes(modelName))
|
|
||||||
.forEach((m) => {
|
|
||||||
const [fullName, displayName] = m.split("=");
|
|
||||||
const [_, providerName] = fullName.split("@");
|
|
||||||
if (providerName === "azure" && !displayName) {
|
|
||||||
const [_, deployId] = (serverConfig?.azureUrl ?? "").split(
|
|
||||||
"deployments/",
|
|
||||||
);
|
|
||||||
if (deployId) {
|
|
||||||
realDeployName = deployId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (realDeployName) {
|
|
||||||
console.log("[Replace with DeployId", realDeployName);
|
|
||||||
path = path.replaceAll(modelName, realDeployName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
path = makeAzurePath(path, serverConfig.azureApiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchUrl = cloudflareAIGatewayUrl(`${baseUrl}/${path}`);
|
const fetchUrl = `${baseUrl}/${path}`;
|
||||||
console.log("fetchUrl", fetchUrl);
|
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -119,24 +83,17 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
// #1815 try to refuse gpt4 request
|
// #1815 try to refuse gpt4 request
|
||||||
if (serverConfig.customModels && req.body) {
|
if (serverConfig.customModels && req.body) {
|
||||||
try {
|
try {
|
||||||
|
const modelTable = collectModelTable(
|
||||||
|
DEFAULT_MODELS,
|
||||||
|
serverConfig.customModels,
|
||||||
|
);
|
||||||
const clonedBody = await req.text();
|
const clonedBody = await req.text();
|
||||||
fetchOptions.body = clonedBody;
|
fetchOptions.body = clonedBody;
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
||||||
|
|
||||||
// not undefined and is false
|
// not undefined and is false
|
||||||
if (
|
if (modelTable[jsonBody?.model ?? ""].available === false) {
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.OpenAI as string,
|
|
||||||
) ||
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Azure as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -155,16 +112,16 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
const res = await fetch(fetchUrl, fetchOptions);
|
||||||
|
|
||||||
// Extract the OpenAI-Organization header from the response
|
// Extract the OpenAI-Organization header from the response
|
||||||
const openaiOrganizationHeader = res.headers.get("OpenAI-Organization");
|
const openaiOrganizationHeader = res.headers.get("OpenAI-Organization");
|
||||||
|
|
||||||
// Check if serverConfig.openaiOrgId is defined and not an empty string
|
// Check if serverConfig.openaiOrgId is defined and not an empty string
|
||||||
if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") {
|
if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") {
|
||||||
// If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present
|
// If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present
|
||||||
console.log("[Org ID]", openaiOrganizationHeader);
|
console.log("[Org ID]", openaiOrganizationHeader);
|
||||||
} else {
|
} else {
|
||||||
console.log("[Org ID] is not set up.");
|
console.log("[Org ID] is not set up.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
// to prevent browser prompt for credentials
|
||||||
const newHeaders = new Headers(res.headers);
|
const newHeaders = new Headers(res.headers);
|
||||||
@@ -172,6 +129,7 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
// to disable nginx buffering
|
// to disable nginx buffering
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
newHeaders.set("X-Accel-Buffering", "no");
|
||||||
|
|
||||||
|
|
||||||
// Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV)
|
// Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV)
|
||||||
// Also, this is to prevent the header from being sent to the client
|
// Also, this is to prevent the header from being sent to the client
|
||||||
if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") {
|
if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") {
|
||||||
@@ -184,6 +142,7 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
// The browser will try to decode the response with brotli and fail
|
// The browser will try to decode the response with brotli and fail
|
||||||
newHeaders.delete("content-encoding");
|
newHeaders.delete("content-encoding");
|
||||||
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
return new Response(res.body, {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
statusText: res.statusText,
|
statusText: res.statusText,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const DANGER_CONFIG = {
|
|||||||
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
||||||
disableFastLink: serverConfig.disableFastLink,
|
disableFastLink: serverConfig.disableFastLink,
|
||||||
customModels: serverConfig.customModels,
|
customModels: serverConfig.customModels,
|
||||||
defaultModel: serverConfig.defaultModel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "./auth";
|
import { auth } from "../../auth";
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import {
|
import { GEMINI_BASE_URL, Google, ModelProvider } from "@/app/constant";
|
||||||
ApiPath,
|
|
||||||
GEMINI_BASE_URL,
|
|
||||||
Google,
|
|
||||||
ModelProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
async function handle(
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: { provider: string; path: string[] } },
|
{ params }: { params: { path: string[] } },
|
||||||
) {
|
) {
|
||||||
console.log("[Google Route] params ", params);
|
console.log("[Google Route] params ", params);
|
||||||
|
|
||||||
@@ -21,6 +13,32 @@ export async function handle(
|
|||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
return NextResponse.json({ body: "OK" }, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
let baseUrl = serverConfig.googleUrl || GEMINI_BASE_URL;
|
||||||
|
|
||||||
|
if (!baseUrl.startsWith("http")) {
|
||||||
|
baseUrl = `https://${baseUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseUrl.endsWith("/")) {
|
||||||
|
baseUrl = baseUrl.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = `${req.nextUrl.pathname}`.replaceAll("/api/google/", "");
|
||||||
|
|
||||||
|
console.log("[Proxy] ", path);
|
||||||
|
console.log("[Base Url]", baseUrl);
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(
|
||||||
|
() => {
|
||||||
|
controller.abort();
|
||||||
|
},
|
||||||
|
10 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.GeminiPro);
|
const authResult = auth(req, ModelProvider.GeminiPro);
|
||||||
if (authResult.error) {
|
if (authResult.error) {
|
||||||
return NextResponse.json(authResult, {
|
return NextResponse.json(authResult, {
|
||||||
@@ -31,9 +49,9 @@ export async function handle(
|
|||||||
const bearToken = req.headers.get("Authorization") ?? "";
|
const bearToken = req.headers.get("Authorization") ?? "";
|
||||||
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
|
|
||||||
const apiKey = token ? token : serverConfig.googleApiKey;
|
const key = token ? token : serverConfig.googleApiKey;
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!key) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -44,63 +62,8 @@ export async function handle(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const response = await request(req, apiKey);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Google] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET = handle;
|
const fetchUrl = `${baseUrl}/${path}?key=${key}`;
|
||||||
export const POST = handle;
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
||||||
export const preferredRegion = [
|
|
||||||
"bom1",
|
|
||||||
"cle1",
|
|
||||||
"cpt1",
|
|
||||||
"gru1",
|
|
||||||
"hnd1",
|
|
||||||
"iad1",
|
|
||||||
"icn1",
|
|
||||||
"kix1",
|
|
||||||
"pdx1",
|
|
||||||
"sfo1",
|
|
||||||
"sin1",
|
|
||||||
"syd1",
|
|
||||||
];
|
|
||||||
|
|
||||||
async function request(req: NextRequest, apiKey: string) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.googleUrl || GEMINI_BASE_URL;
|
|
||||||
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Google, "");
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
const fetchUrl = `${baseUrl}${path}?key=${apiKey}${
|
|
||||||
req?.nextUrl?.searchParams?.get("alt") === "sse" ? "&alt=sse" : ""
|
|
||||||
}`;
|
|
||||||
|
|
||||||
console.log("[Fetch Url] ", fetchUrl);
|
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -132,3 +95,22 @@ async function request(req: NextRequest, apiKey: string) {
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
export const preferredRegion = [
|
||||||
|
"bom1",
|
||||||
|
"cle1",
|
||||||
|
"cpt1",
|
||||||
|
"gru1",
|
||||||
|
"hnd1",
|
||||||
|
"iad1",
|
||||||
|
"icn1",
|
||||||
|
"kix1",
|
||||||
|
"pdx1",
|
||||||
|
"sfo1",
|
||||||
|
"sin1",
|
||||||
|
"syd1",
|
||||||
|
];
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
Iflytek,
|
|
||||||
IFLYTEK_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
// iflytek
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Iflytek Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Iflytek);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Iflytek] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
// iflytek use base url or just remove the path
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Iflytek, "");
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.iflytekUrl || IFLYTEK_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}${path}`;
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: req.headers.get("Authorization") ?? "",
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
// try to refuse some request to some models
|
|
||||||
if (serverConfig.customModels && req.body) {
|
|
||||||
try {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
fetchOptions.body = clonedBody;
|
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
|
||||||
|
|
||||||
// not undefined and is false
|
|
||||||
if (
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Iflytek as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 403,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Iflytek] filter`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
Moonshot,
|
|
||||||
MOONSHOT_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import type { RequestPayload } from "@/app/client/platforms/openai";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Moonshot Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Moonshot);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Moonshot] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
// alibaba use base url or just remove the path
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Moonshot, "");
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.moonshotUrl || MOONSHOT_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy] ", path);
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}${path}`;
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: req.headers.get("Authorization") ?? "",
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
// #1815 try to refuse some request to some models
|
|
||||||
if (serverConfig.customModels && req.body) {
|
|
||||||
try {
|
|
||||||
const clonedBody = await req.text();
|
|
||||||
fetchOptions.body = clonedBody;
|
|
||||||
|
|
||||||
const jsonBody = JSON.parse(clonedBody) as { model?: string };
|
|
||||||
|
|
||||||
// not undefined and is false
|
|
||||||
if (
|
|
||||||
isModelAvailableInServer(
|
|
||||||
serverConfig.customModels,
|
|
||||||
jsonBody?.model as string,
|
|
||||||
ServiceProvider.Moonshot as string,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `you are not allowed to use ${jsonBody?.model} model`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 403,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Moonshot] filter`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,8 @@ import { getServerSideConfig } from "@/app/config/server";
|
|||||||
import { ModelProvider, OpenaiPath } from "@/app/constant";
|
import { ModelProvider, OpenaiPath } from "@/app/constant";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { auth } from "./auth";
|
import { auth } from "../../auth";
|
||||||
import { requestOpenai } from "./common";
|
import { requestOpenai } from "../../common";
|
||||||
|
|
||||||
const ALLOWD_PATH = new Set(Object.values(OpenaiPath));
|
const ALLOWD_PATH = new Set(Object.values(OpenaiPath));
|
||||||
|
|
||||||
@@ -13,14 +13,14 @@ function getModels(remoteModelRes: OpenAIListModelResponse) {
|
|||||||
|
|
||||||
if (config.disableGPT4) {
|
if (config.disableGPT4) {
|
||||||
remoteModelRes.data = remoteModelRes.data.filter(
|
remoteModelRes.data = remoteModelRes.data.filter(
|
||||||
(m) => !m.id.startsWith("gpt-4") || m.id.startsWith("gpt-4o-mini"),
|
(m) => !m.id.startsWith("gpt-4"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return remoteModelRes;
|
return remoteModelRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handle(
|
async function handle(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: { path: string[] } },
|
{ params }: { params: { path: string[] } },
|
||||||
) {
|
) {
|
||||||
@@ -70,3 +70,27 @@ export async function handle(
|
|||||||
return NextResponse.json(prettyObject(e));
|
return NextResponse.json(prettyObject(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
export const preferredRegion = [
|
||||||
|
"arn1",
|
||||||
|
"bom1",
|
||||||
|
"cdg1",
|
||||||
|
"cle1",
|
||||||
|
"cpt1",
|
||||||
|
"dub1",
|
||||||
|
"fra1",
|
||||||
|
"gru1",
|
||||||
|
"hnd1",
|
||||||
|
"iad1",
|
||||||
|
"icn1",
|
||||||
|
"kix1",
|
||||||
|
"lhr1",
|
||||||
|
"pdx1",
|
||||||
|
"sfo1",
|
||||||
|
"sin1",
|
||||||
|
"syd1",
|
||||||
|
];
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import { ModelProvider, STABILITY_BASE_URL } from "@/app/constant";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
|
|
||||||
export async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Stability] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.stabilityUrl || STABILITY_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = `${req.nextUrl.pathname}`.replaceAll("/api/stability/", "");
|
|
||||||
|
|
||||||
console.log("[Stability Proxy] ", path);
|
|
||||||
console.log("[Stability Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Stability);
|
|
||||||
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const bearToken = req.headers.get("Authorization") ?? "";
|
|
||||||
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
|
||||||
|
|
||||||
const key = token ? token : serverConfig.stabilityApiKey;
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
message: `missing STABILITY_API_KEY in server env vars`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchUrl = `${baseUrl}/${path}`;
|
|
||||||
console.log("[Stability Url] ", fetchUrl);
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": req.headers.get("Content-Type") || "multipart/form-data",
|
|
||||||
Accept: req.headers.get("Accept") || "application/json",
|
|
||||||
Authorization: `Bearer ${key}`,
|
|
||||||
},
|
|
||||||
method: req.method,
|
|
||||||
body: req.body,
|
|
||||||
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
import {
|
|
||||||
TENCENT_BASE_URL,
|
|
||||||
ApiPath,
|
|
||||||
ModelProvider,
|
|
||||||
ServiceProvider,
|
|
||||||
Tencent,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { auth } from "@/app/api/auth";
|
|
||||||
import { isModelAvailableInServer } from "@/app/utils/model";
|
|
||||||
import { getHeader } from "@/app/utils/tencent";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
console.log("[Tencent Route] params ", params);
|
|
||||||
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authResult = auth(req, ModelProvider.Hunyuan);
|
|
||||||
if (authResult.error) {
|
|
||||||
return NextResponse.json(authResult, {
|
|
||||||
status: 401,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(req);
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Tencent] ", e);
|
|
||||||
return NextResponse.json(prettyObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET = handle;
|
|
||||||
export const POST = handle;
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
||||||
export const preferredRegion = [
|
|
||||||
"arn1",
|
|
||||||
"bom1",
|
|
||||||
"cdg1",
|
|
||||||
"cle1",
|
|
||||||
"cpt1",
|
|
||||||
"dub1",
|
|
||||||
"fra1",
|
|
||||||
"gru1",
|
|
||||||
"hnd1",
|
|
||||||
"iad1",
|
|
||||||
"icn1",
|
|
||||||
"kix1",
|
|
||||||
"lhr1",
|
|
||||||
"pdx1",
|
|
||||||
"sfo1",
|
|
||||||
"sin1",
|
|
||||||
"syd1",
|
|
||||||
];
|
|
||||||
|
|
||||||
async function request(req: NextRequest) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
let baseUrl = serverConfig.tencentUrl || TENCENT_BASE_URL;
|
|
||||||
|
|
||||||
if (!baseUrl.startsWith("http")) {
|
|
||||||
baseUrl = `https://${baseUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Base Url]", baseUrl);
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(
|
|
||||||
() => {
|
|
||||||
controller.abort();
|
|
||||||
},
|
|
||||||
10 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchUrl = baseUrl;
|
|
||||||
|
|
||||||
const body = await req.text();
|
|
||||||
const headers = await getHeader(
|
|
||||||
body,
|
|
||||||
serverConfig.tencentSecretId as string,
|
|
||||||
serverConfig.tencentSecretKey as string,
|
|
||||||
);
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
headers,
|
|
||||||
method: req.method,
|
|
||||||
body,
|
|
||||||
redirect: "manual",
|
|
||||||
// @ts-ignore
|
|
||||||
duplex: "half",
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(fetchUrl, fetchOptions);
|
|
||||||
|
|
||||||
// to prevent browser prompt for credentials
|
|
||||||
const newHeaders = new Headers(res.headers);
|
|
||||||
newHeaders.delete("www-authenticate");
|
|
||||||
// to disable nginx buffering
|
|
||||||
newHeaders.set("X-Accel-Buffering", "no");
|
|
||||||
|
|
||||||
return new Response(res.body, {
|
|
||||||
status: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: newHeaders,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
|
import { STORAGE_KEY } from "../../../constant";
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
|
||||||
|
|
||||||
const config = getServerSideConfig();
|
|
||||||
|
|
||||||
const mergedAllowedWebDavEndpoints = [
|
|
||||||
...internalAllowedWebDavEndpoints,
|
|
||||||
...config.allowedWebDevEndpoints,
|
|
||||||
].filter((domain) => Boolean(domain.trim()));
|
|
||||||
|
|
||||||
const normalizeUrl = (url: string) => {
|
|
||||||
try {
|
|
||||||
return new URL(url);
|
|
||||||
} catch (err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handle(
|
async function handle(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: { path: string[] } },
|
{ params }: { params: { path: string[] } },
|
||||||
@@ -29,24 +12,9 @@ async function handle(
|
|||||||
|
|
||||||
const requestUrl = new URL(req.url);
|
const requestUrl = new URL(req.url);
|
||||||
let endpoint = requestUrl.searchParams.get("endpoint");
|
let endpoint = requestUrl.searchParams.get("endpoint");
|
||||||
let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method;
|
|
||||||
|
|
||||||
// Validate the endpoint to prevent potential SSRF attacks
|
// Validate the endpoint to prevent potential SSRF attacks
|
||||||
if (
|
if (!endpoint || !endpoint.startsWith("/")) {
|
||||||
!endpoint ||
|
|
||||||
!mergedAllowedWebDavEndpoints.some((allowedEndpoint) => {
|
|
||||||
const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
|
|
||||||
const normalizedEndpoint = normalizeUrl(endpoint as string);
|
|
||||||
|
|
||||||
return (
|
|
||||||
normalizedEndpoint &&
|
|
||||||
normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
|
|
||||||
normalizedEndpoint.pathname.startsWith(
|
|
||||||
normalizedAllowedEndpoint.pathname,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -57,20 +25,11 @@ async function handle(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!endpoint?.endsWith("/")) {
|
|
||||||
endpoint += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpointPath = params.path.join("/");
|
const endpointPath = params.path.join("/");
|
||||||
const targetPath = `${endpoint}${endpointPath}`;
|
const targetPath = `${endpoint}/${endpointPath}`;
|
||||||
|
|
||||||
// only allow MKCOL, GET, PUT
|
// only allow MKCOL, GET, PUT
|
||||||
if (
|
if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
|
||||||
proxy_method !== "MKCOL" &&
|
|
||||||
proxy_method !== "GET" &&
|
|
||||||
proxy_method !== "PUT"
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -83,7 +42,10 @@ async function handle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for MKCOL request, only allow request ${folder}
|
// for MKCOL request, only allow request ${folder}
|
||||||
if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) {
|
if (
|
||||||
|
req.method === "MKCOL" &&
|
||||||
|
!targetPath.endsWith(folder)
|
||||||
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -96,7 +58,10 @@ async function handle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for GET request, only allow request ending with fileName
|
// for GET request, only allow request ending with fileName
|
||||||
if (proxy_method === "GET" && !targetPath.endsWith(fileName)) {
|
if (
|
||||||
|
req.method === "GET" &&
|
||||||
|
!targetPath.endsWith(fileName)
|
||||||
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -109,7 +74,10 @@ async function handle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for PUT request, only allow request ending with fileName
|
// for PUT request, only allow request ending with fileName
|
||||||
if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) {
|
if (
|
||||||
|
req.method === "PUT" &&
|
||||||
|
!targetPath.endsWith(fileName)
|
||||||
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -121,9 +89,9 @@ async function handle(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetUrl = targetPath;
|
const targetUrl = `${endpoint}/${endpointPath}`;
|
||||||
|
|
||||||
const method = proxy_method || req.method;
|
const method = req.method;
|
||||||
const shouldNotHaveBody = ["get", "head"].includes(
|
const shouldNotHaveBody = ["get", "head"].includes(
|
||||||
method?.toLowerCase() ?? "",
|
method?.toLowerCase() ?? "",
|
||||||
);
|
);
|
||||||
@@ -133,34 +101,23 @@ async function handle(
|
|||||||
authorization: req.headers.get("authorization") ?? "",
|
authorization: req.headers.get("authorization") ?? "",
|
||||||
},
|
},
|
||||||
body: shouldNotHaveBody ? null : req.body,
|
body: shouldNotHaveBody ? null : req.body,
|
||||||
redirect: "manual",
|
redirect: 'manual',
|
||||||
method,
|
method,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
duplex: "half",
|
duplex: "half",
|
||||||
};
|
};
|
||||||
|
|
||||||
let fetchResult;
|
const fetchResult = await fetch(targetUrl, fetchOptions);
|
||||||
|
|
||||||
try {
|
console.log("[Any Proxy]", targetUrl, {
|
||||||
fetchResult = await fetch(targetUrl, fetchOptions);
|
status: fetchResult.status,
|
||||||
} finally {
|
statusText: fetchResult.statusText,
|
||||||
console.log(
|
});
|
||||||
"[Any Proxy]",
|
|
||||||
targetUrl,
|
|
||||||
{
|
|
||||||
method: method,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: fetchResult?.status,
|
|
||||||
statusText: fetchResult?.statusText,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetchResult;
|
return fetchResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PUT = handle;
|
export const POST = handle;
|
||||||
export const GET = handle;
|
export const GET = handle;
|
||||||
export const OPTIONS = handle;
|
export const OPTIONS = handle;
|
||||||
|
|
||||||
|
|||||||
9
app/azure.ts
Normal file
9
app/azure.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function makeAzurePath(path: string, apiVersion: string) {
|
||||||
|
// should omit /v1 prefix
|
||||||
|
path = path.replaceAll("v1/", "");
|
||||||
|
|
||||||
|
// should add api-key to query string
|
||||||
|
path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
@@ -6,16 +6,9 @@ import {
|
|||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store";
|
import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store";
|
||||||
import { ChatGPTApi, DalleRequestPayload } from "./platforms/openai";
|
import { ChatGPTApi } from "./platforms/openai";
|
||||||
import { GeminiProApi } from "./platforms/google";
|
import { GeminiProApi } from "./platforms/google";
|
||||||
import { ClaudeApi } from "./platforms/anthropic";
|
import { ClaudeApi } from "./platforms/anthropic";
|
||||||
import { ErnieApi } from "./platforms/baidu";
|
|
||||||
import { DoubaoApi } from "./platforms/bytedance";
|
|
||||||
import { QwenApi } from "./platforms/alibaba";
|
|
||||||
import { HunyuanApi } from "./platforms/tencent";
|
|
||||||
import { MoonshotApi } from "./platforms/moonshot";
|
|
||||||
import { SparkApi } from "./platforms/iflytek";
|
|
||||||
|
|
||||||
export const ROLES = ["system", "user", "assistant"] as const;
|
export const ROLES = ["system", "user", "assistant"] as const;
|
||||||
export type MessageRole = (typeof ROLES)[number];
|
export type MessageRole = (typeof ROLES)[number];
|
||||||
|
|
||||||
@@ -37,15 +30,11 @@ export interface RequestMessage {
|
|||||||
|
|
||||||
export interface LLMConfig {
|
export interface LLMConfig {
|
||||||
model: string;
|
model: string;
|
||||||
providerName?: string;
|
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
top_p?: number;
|
top_p?: number;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
presence_penalty?: number;
|
presence_penalty?: number;
|
||||||
frequency_penalty?: number;
|
frequency_penalty?: number;
|
||||||
size?: DalleRequestPayload["size"];
|
|
||||||
quality?: DalleRequestPayload["quality"];
|
|
||||||
style?: DalleRequestPayload["style"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatOptions {
|
export interface ChatOptions {
|
||||||
@@ -65,17 +54,14 @@ export interface LLMUsage {
|
|||||||
|
|
||||||
export interface LLMModel {
|
export interface LLMModel {
|
||||||
name: string;
|
name: string;
|
||||||
displayName?: string;
|
|
||||||
available: boolean;
|
available: boolean;
|
||||||
provider: LLMModelProvider;
|
provider: LLMModelProvider;
|
||||||
sorted: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LLMModelProvider {
|
export interface LLMModelProvider {
|
||||||
id: string;
|
id: string;
|
||||||
providerName: string;
|
providerName: string;
|
||||||
providerType: string;
|
providerType: string;
|
||||||
sorted: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class LLMApi {
|
export abstract class LLMApi {
|
||||||
@@ -116,24 +102,6 @@ export class ClientApi {
|
|||||||
case ModelProvider.Claude:
|
case ModelProvider.Claude:
|
||||||
this.llm = new ClaudeApi();
|
this.llm = new ClaudeApi();
|
||||||
break;
|
break;
|
||||||
case ModelProvider.Ernie:
|
|
||||||
this.llm = new ErnieApi();
|
|
||||||
break;
|
|
||||||
case ModelProvider.Doubao:
|
|
||||||
this.llm = new DoubaoApi();
|
|
||||||
break;
|
|
||||||
case ModelProvider.Qwen:
|
|
||||||
this.llm = new QwenApi();
|
|
||||||
break;
|
|
||||||
case ModelProvider.Hunyuan:
|
|
||||||
this.llm = new HunyuanApi();
|
|
||||||
break;
|
|
||||||
case ModelProvider.Moonshot:
|
|
||||||
this.llm = new MoonshotApi();
|
|
||||||
break;
|
|
||||||
case ModelProvider.Iflytek:
|
|
||||||
this.llm = new SparkApi();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
this.llm = new ChatGPTApi();
|
this.llm = new ChatGPTApi();
|
||||||
}
|
}
|
||||||
@@ -185,122 +153,39 @@ export class ClientApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBearerToken(
|
|
||||||
apiKey: string,
|
|
||||||
noBearer: boolean = false,
|
|
||||||
): string {
|
|
||||||
return validString(apiKey)
|
|
||||||
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validString(x: string): boolean {
|
|
||||||
return x?.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHeaders() {
|
export function getHeaders() {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
const chatStore = useChatStore.getState();
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
};
|
};
|
||||||
|
const modelConfig = useChatStore.getState().currentSession().mask.modelConfig;
|
||||||
|
const isGoogle = modelConfig.model.startsWith("gemini");
|
||||||
|
const isAzure = accessStore.provider === ServiceProvider.Azure;
|
||||||
|
const authHeader = isAzure ? "api-key" : "Authorization";
|
||||||
|
const apiKey = isGoogle
|
||||||
|
? accessStore.googleApiKey
|
||||||
|
: isAzure
|
||||||
|
? accessStore.azureApiKey
|
||||||
|
: accessStore.openaiApiKey;
|
||||||
const clientConfig = getClientConfig();
|
const clientConfig = getClientConfig();
|
||||||
|
const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
|
||||||
|
const validString = (x: string) => x && x.length > 0;
|
||||||
|
|
||||||
function getConfig() {
|
|
||||||
const modelConfig = chatStore.currentSession().mask.modelConfig;
|
|
||||||
const isGoogle = modelConfig.providerName == ServiceProvider.Google;
|
|
||||||
const isAzure = modelConfig.providerName === ServiceProvider.Azure;
|
|
||||||
const isAnthropic = modelConfig.providerName === ServiceProvider.Anthropic;
|
|
||||||
const isBaidu = modelConfig.providerName == ServiceProvider.Baidu;
|
|
||||||
const isByteDance = modelConfig.providerName === ServiceProvider.ByteDance;
|
|
||||||
const isAlibaba = modelConfig.providerName === ServiceProvider.Alibaba;
|
|
||||||
const isMoonshot = modelConfig.providerName === ServiceProvider.Moonshot;
|
|
||||||
const isIflytek = modelConfig.providerName === ServiceProvider.Iflytek;
|
|
||||||
const isEnabledAccessControl = accessStore.enabledAccessControl();
|
|
||||||
const apiKey = isGoogle
|
|
||||||
? accessStore.googleApiKey
|
|
||||||
: isAzure
|
|
||||||
? accessStore.azureApiKey
|
|
||||||
: isAnthropic
|
|
||||||
? accessStore.anthropicApiKey
|
|
||||||
: isByteDance
|
|
||||||
? accessStore.bytedanceApiKey
|
|
||||||
: isAlibaba
|
|
||||||
? accessStore.alibabaApiKey
|
|
||||||
: isMoonshot
|
|
||||||
? accessStore.moonshotApiKey
|
|
||||||
: isIflytek
|
|
||||||
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
|
|
||||||
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
|
|
||||||
: ""
|
|
||||||
: accessStore.openaiApiKey;
|
|
||||||
return {
|
|
||||||
isGoogle,
|
|
||||||
isAzure,
|
|
||||||
isAnthropic,
|
|
||||||
isBaidu,
|
|
||||||
isByteDance,
|
|
||||||
isAlibaba,
|
|
||||||
isMoonshot,
|
|
||||||
isIflytek,
|
|
||||||
apiKey,
|
|
||||||
isEnabledAccessControl,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthHeader(): string {
|
|
||||||
return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
isGoogle,
|
|
||||||
isAzure,
|
|
||||||
isAnthropic,
|
|
||||||
isBaidu,
|
|
||||||
apiKey,
|
|
||||||
isEnabledAccessControl,
|
|
||||||
} = getConfig();
|
|
||||||
// when using google api in app, not set auth header
|
// when using google api in app, not set auth header
|
||||||
if (isGoogle && clientConfig?.isApp) return headers;
|
if (!(isGoogle && clientConfig?.isApp)) {
|
||||||
// when using baidu api in app, not set auth header
|
// use user's api key first
|
||||||
if (isBaidu && clientConfig?.isApp) return headers;
|
if (validString(apiKey)) {
|
||||||
|
headers[authHeader] = makeBearer(apiKey);
|
||||||
const authHeader = getAuthHeader();
|
} else if (
|
||||||
|
accessStore.enabledAccessControl() &&
|
||||||
const bearerToken = getBearerToken(apiKey, isAzure || isAnthropic);
|
validString(accessStore.accessCode)
|
||||||
|
) {
|
||||||
if (bearerToken) {
|
headers[authHeader] = makeBearer(
|
||||||
headers[authHeader] = bearerToken;
|
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||||
} else if (isEnabledAccessControl && validString(accessStore.accessCode)) {
|
);
|
||||||
headers["Authorization"] = getBearerToken(
|
}
|
||||||
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClientApi(provider: ServiceProvider): ClientApi {
|
|
||||||
switch (provider) {
|
|
||||||
case ServiceProvider.Google:
|
|
||||||
return new ClientApi(ModelProvider.GeminiPro);
|
|
||||||
case ServiceProvider.Anthropic:
|
|
||||||
return new ClientApi(ModelProvider.Claude);
|
|
||||||
case ServiceProvider.Baidu:
|
|
||||||
return new ClientApi(ModelProvider.Ernie);
|
|
||||||
case ServiceProvider.ByteDance:
|
|
||||||
return new ClientApi(ModelProvider.Doubao);
|
|
||||||
case ServiceProvider.Alibaba:
|
|
||||||
return new ClientApi(ModelProvider.Qwen);
|
|
||||||
case ServiceProvider.Tencent:
|
|
||||||
return new ClientApi(ModelProvider.Hunyuan);
|
|
||||||
case ServiceProvider.Moonshot:
|
|
||||||
return new ClientApi(ModelProvider.Moonshot);
|
|
||||||
case ServiceProvider.Iflytek:
|
|
||||||
return new ClientApi(ModelProvider.Iflytek);
|
|
||||||
default:
|
|
||||||
return new ClientApi(ModelProvider.GPT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,268 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import {
|
|
||||||
ApiPath,
|
|
||||||
Alibaba,
|
|
||||||
ALIBABA_BASE_URL,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChatOptions,
|
|
||||||
getHeaders,
|
|
||||||
LLMApi,
|
|
||||||
LLMModel,
|
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
|
||||||
object: string;
|
|
||||||
data: Array<{
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
root: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestInput {
|
|
||||||
messages: {
|
|
||||||
role: "system" | "user" | "assistant";
|
|
||||||
content: string | MultimodalContent[];
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
interface RequestParam {
|
|
||||||
result_format: string;
|
|
||||||
incremental_output?: boolean;
|
|
||||||
temperature: number;
|
|
||||||
repetition_penalty?: number;
|
|
||||||
top_p: number;
|
|
||||||
max_tokens?: number;
|
|
||||||
}
|
|
||||||
interface RequestPayload {
|
|
||||||
model: string;
|
|
||||||
input: RequestInput;
|
|
||||||
parameters: RequestParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QwenApi implements LLMApi {
|
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.alibabaUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
baseUrl = isApp ? ALIBABA_BASE_URL : ApiPath.Alibaba;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Alibaba)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
return [baseUrl, path].join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
extractMessage(res: any) {
|
|
||||||
return res?.output?.choices?.at(0)?.message?.content ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const messages = options.messages.map((v) => ({
|
|
||||||
role: v.role,
|
|
||||||
content: getMessageTextContent(v),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const requestPayload: RequestPayload = {
|
|
||||||
model: modelConfig.model,
|
|
||||||
input: {
|
|
||||||
messages,
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
result_format: "message",
|
|
||||||
incremental_output: shouldStream,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
// max_tokens: modelConfig.max_tokens,
|
|
||||||
top_p: modelConfig.top_p === 1 ? 0.99 : modelConfig.top_p, // qwen top_p is should be < 1
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const chatPath = this.path(Alibaba.ChatPath);
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: {
|
|
||||||
...getHeaders(),
|
|
||||||
"X-DashScope-SSE": shouldStream ? "enable" : "disable",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
if (responseText?.length === 0) {
|
|
||||||
options.onError?.(new Error("empty response from server"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animaion
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log(
|
|
||||||
"[Alibaba] request response content type: ",
|
|
||||||
contentType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
responseTexts.push(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
responseText = responseTexts.join("\n\n");
|
|
||||||
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const choices = json.output.choices as Array<{
|
|
||||||
message: { content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.message?.content;
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = this.extractMessage(resJson);
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export { Alibaba };
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant";
|
import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant";
|
||||||
import { ChatOptions, getHeaders, LLMApi, MultimodalContent } from "../api";
|
import { ChatOptions, LLMApi, MultimodalContent } from "../api";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { DEFAULT_API_HOST } from "@/app/constant";
|
import { DEFAULT_API_HOST } from "@/app/constant";
|
||||||
|
import { RequestMessage } from "@/app/typing";
|
||||||
import {
|
import {
|
||||||
EventStreamContentType,
|
EventStreamContentType,
|
||||||
fetchEventSource,
|
fetchEventSource,
|
||||||
@@ -11,8 +12,6 @@ import {
|
|||||||
import Locale from "../../locales";
|
import Locale from "../../locales";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { getMessageTextContent, isVisionModel } from "@/app/utils";
|
import { getMessageTextContent, isVisionModel } from "@/app/utils";
|
||||||
import { preProcessImageContent } from "@/app/utils/chat";
|
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
|
||||||
|
|
||||||
export type MultiBlockContent = {
|
export type MultiBlockContent = {
|
||||||
type: "image" | "text";
|
type: "image" | "text";
|
||||||
@@ -93,12 +92,7 @@ export class ClaudeApi implements LLMApi {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// try get base64image from local cache image_url
|
const messages = [...options.messages];
|
||||||
const messages: ChatOptions["messages"] = [];
|
|
||||||
for (const v of options.messages) {
|
|
||||||
const content = await preProcessImageContent(v.content);
|
|
||||||
messages.push({ role: v.role, content });
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = ["system", "user"];
|
const keys = ["system", "user"];
|
||||||
|
|
||||||
@@ -167,13 +161,6 @@ export class ClaudeApi implements LLMApi {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (prompt[0]?.role === "assistant") {
|
|
||||||
prompt.unshift({
|
|
||||||
role: "user",
|
|
||||||
content: ";",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestBody: AnthropicChatRequest = {
|
const requestBody: AnthropicChatRequest = {
|
||||||
messages: prompt,
|
messages: prompt,
|
||||||
stream: shouldStream,
|
stream: shouldStream,
|
||||||
@@ -196,10 +183,11 @@ export class ClaudeApi implements LLMApi {
|
|||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
headers: {
|
headers: {
|
||||||
...getHeaders(), // get common headers
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
"x-api-key": accessStore.anthropicApiKey,
|
||||||
"anthropic-version": accessStore.anthropicApiVersion,
|
"anthropic-version": accessStore.anthropicApiVersion,
|
||||||
// do not send `anthropicApiKey` in browser!!!
|
Authorization: getAuthKey(accessStore.anthropicApiKey),
|
||||||
// Authorization: getAuthKey(accessStore.anthropicApiKey),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -360,11 +348,7 @@ export class ClaudeApi implements LLMApi {
|
|||||||
path(path: string): string {
|
path(path: string): string {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
|
|
||||||
let baseUrl: string = "";
|
let baseUrl: string = accessStore.anthropicUrl;
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.anthropicUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if endpoint is empty, use default endpoint
|
// if endpoint is empty, use default endpoint
|
||||||
if (baseUrl.trim().length === 0) {
|
if (baseUrl.trim().length === 0) {
|
||||||
@@ -381,8 +365,7 @@ export class ClaudeApi implements LLMApi {
|
|||||||
|
|
||||||
baseUrl = trimEnd(baseUrl, "/");
|
baseUrl = trimEnd(baseUrl, "/");
|
||||||
|
|
||||||
// try rebuild url, when using cloudflare ai gateway in client
|
return `${baseUrl}/${path}`;
|
||||||
return cloudflareAIGatewayUrl(`${baseUrl}/${path}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,3 +378,27 @@ function trimEnd(s: string, end = " ") {
|
|||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bearer(value: string) {
|
||||||
|
return `Bearer ${value.trim()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthKey(apiKey = "") {
|
||||||
|
const accessStore = useAccessStore.getState();
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
let authKey = "";
|
||||||
|
|
||||||
|
if (apiKey) {
|
||||||
|
// use user's api key first
|
||||||
|
authKey = bearer(apiKey);
|
||||||
|
} else if (
|
||||||
|
accessStore.enabledAccessControl() &&
|
||||||
|
!isApp &&
|
||||||
|
!!accessStore.accessCode
|
||||||
|
) {
|
||||||
|
// or use access code
|
||||||
|
authKey = bearer(ACCESS_CODE_PREFIX + accessStore.accessCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return authKey;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,281 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import {
|
|
||||||
ApiPath,
|
|
||||||
Baidu,
|
|
||||||
BAIDU_BASE_URL,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
import { getAccessToken } from "@/app/utils/baidu";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChatOptions,
|
|
||||||
getHeaders,
|
|
||||||
LLMApi,
|
|
||||||
LLMModel,
|
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
|
||||||
object: string;
|
|
||||||
data: Array<{
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
root: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestPayload {
|
|
||||||
messages: {
|
|
||||||
role: "system" | "user" | "assistant";
|
|
||||||
content: string | MultimodalContent[];
|
|
||||||
}[];
|
|
||||||
stream?: boolean;
|
|
||||||
model: string;
|
|
||||||
temperature: number;
|
|
||||||
presence_penalty: number;
|
|
||||||
frequency_penalty: number;
|
|
||||||
top_p: number;
|
|
||||||
max_tokens?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ErnieApi implements LLMApi {
|
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.baiduUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
// do not use proxy for baidubce api
|
|
||||||
baseUrl = isApp ? BAIDU_BASE_URL : ApiPath.Baidu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Baidu)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
return [baseUrl, path].join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const messages = options.messages.map((v) => ({
|
|
||||||
// "error_code": 336006, "error_msg": "the role of message with even index in the messages must be user or function",
|
|
||||||
role: v.role === "system" ? "user" : v.role,
|
|
||||||
content: getMessageTextContent(v),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// "error_code": 336006, "error_msg": "the length of messages must be an odd number",
|
|
||||||
if (messages.length % 2 === 0) {
|
|
||||||
if (messages.at(0)?.role === "user") {
|
|
||||||
messages.splice(1, 0, {
|
|
||||||
role: "assistant",
|
|
||||||
content: " ",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
messages.unshift({
|
|
||||||
role: "user",
|
|
||||||
content: " ",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const requestPayload: RequestPayload = {
|
|
||||||
messages,
|
|
||||||
stream: shouldStream,
|
|
||||||
model: modelConfig.model,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
presence_penalty: modelConfig.presence_penalty,
|
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("[Request] Baidu payload: ", requestPayload);
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let chatPath = this.path(Baidu.ChatPath(modelConfig.model));
|
|
||||||
|
|
||||||
// getAccessToken can not run in browser, because cors error
|
|
||||||
if (!!getClientConfig()?.isApp) {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
if (accessStore.isValidBaidu()) {
|
|
||||||
const { access_token } = await getAccessToken(
|
|
||||||
accessStore.baiduApiKey,
|
|
||||||
accessStore.baiduSecretKey,
|
|
||||||
);
|
|
||||||
chatPath = `${chatPath}${
|
|
||||||
chatPath.includes("?") ? "&" : "?"
|
|
||||||
}access_token=${access_token}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
if (responseText?.length === 0) {
|
|
||||||
options.onError?.(new Error("empty response from server"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animaion
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log("[Baidu] request response content type: ", contentType);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
responseTexts.push(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
responseText = responseTexts.join("\n\n");
|
|
||||||
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const delta = json?.result;
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = resJson?.result;
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export { Baidu };
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import {
|
|
||||||
ApiPath,
|
|
||||||
ByteDance,
|
|
||||||
BYTEDANCE_BASE_URL,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChatOptions,
|
|
||||||
getHeaders,
|
|
||||||
LLMApi,
|
|
||||||
LLMModel,
|
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
|
||||||
object: string;
|
|
||||||
data: Array<{
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
root: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestPayload {
|
|
||||||
messages: {
|
|
||||||
role: "system" | "user" | "assistant";
|
|
||||||
content: string | MultimodalContent[];
|
|
||||||
}[];
|
|
||||||
stream?: boolean;
|
|
||||||
model: string;
|
|
||||||
temperature: number;
|
|
||||||
presence_penalty: number;
|
|
||||||
frequency_penalty: number;
|
|
||||||
top_p: number;
|
|
||||||
max_tokens?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DoubaoApi implements LLMApi {
|
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.bytedanceUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
baseUrl = isApp ? BYTEDANCE_BASE_URL : ApiPath.ByteDance;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.ByteDance)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
return [baseUrl, path].join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
extractMessage(res: any) {
|
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const messages = options.messages.map((v) => ({
|
|
||||||
role: v.role,
|
|
||||||
content: getMessageTextContent(v),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const requestPayload: RequestPayload = {
|
|
||||||
messages,
|
|
||||||
stream: shouldStream,
|
|
||||||
model: modelConfig.model,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
presence_penalty: modelConfig.presence_penalty,
|
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
};
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const chatPath = this.path(ByteDance.ChatPath);
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
if (responseText?.length === 0) {
|
|
||||||
options.onError?.(new Error("empty response from server"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animaion
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log(
|
|
||||||
"[ByteDance] request response content type: ",
|
|
||||||
contentType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
responseTexts.push(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
responseText = responseTexts.join("\n\n");
|
|
||||||
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const choices = json.choices as Array<{
|
|
||||||
delta: { content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.delta?.content;
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = this.extractMessage(resJson);
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export { ByteDance };
|
|
||||||
@@ -1,52 +1,15 @@
|
|||||||
import { ApiPath, Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
|
import { Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
|
||||||
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
|
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
import { DEFAULT_API_HOST } from "@/app/constant";
|
import { DEFAULT_API_HOST } from "@/app/constant";
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import {
|
import {
|
||||||
getMessageTextContent,
|
getMessageTextContent,
|
||||||
getMessageImages,
|
getMessageImages,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
} from "@/app/utils";
|
} from "@/app/utils";
|
||||||
import { preProcessImageContent } from "@/app/utils/chat";
|
|
||||||
|
|
||||||
export class GeminiProApi implements LLMApi {
|
export class GeminiProApi implements LLMApi {
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.googleUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
baseUrl = isApp ? DEFAULT_API_HOST + `/api/proxy/google` : ApiPath.Google;
|
|
||||||
}
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Google)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
let chatPath = [baseUrl, path].join("/");
|
|
||||||
|
|
||||||
chatPath += chatPath.includes("?") ? "&alt=sse" : "?alt=sse";
|
|
||||||
// if chatPath.startsWith('http') then add key in query string
|
|
||||||
if (chatPath.startsWith("http") && accessStore.googleApiKey) {
|
|
||||||
chatPath += `&key=${accessStore.googleApiKey}`;
|
|
||||||
}
|
|
||||||
return chatPath;
|
|
||||||
}
|
|
||||||
extractMessage(res: any) {
|
extractMessage(res: any) {
|
||||||
console.log("[Response] gemini-pro response: ", res);
|
console.log("[Response] gemini-pro response: ", res);
|
||||||
|
|
||||||
@@ -57,18 +20,12 @@ export class GeminiProApi implements LLMApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
async chat(options: ChatOptions): Promise<void> {
|
async chat(options: ChatOptions): Promise<void> {
|
||||||
const apiClient = this;
|
// const apiClient = this;
|
||||||
|
const visionModel = isVisionModel(options.config.model);
|
||||||
let multimodal = false;
|
let multimodal = false;
|
||||||
|
const messages = options.messages.map((v) => {
|
||||||
// try get base64image from local cache image_url
|
|
||||||
const _messages: ChatOptions["messages"] = [];
|
|
||||||
for (const v of options.messages) {
|
|
||||||
const content = await preProcessImageContent(v.content);
|
|
||||||
_messages.push({ role: v.role, content });
|
|
||||||
}
|
|
||||||
const messages = _messages.map((v) => {
|
|
||||||
let parts: any[] = [{ text: getMessageTextContent(v) }];
|
let parts: any[] = [{ text: getMessageTextContent(v) }];
|
||||||
if (isVisionModel(options.config.model)) {
|
if (visionModel) {
|
||||||
const images = getMessageImages(v);
|
const images = getMessageImages(v);
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
multimodal = true;
|
multimodal = true;
|
||||||
@@ -108,9 +65,6 @@ export class GeminiProApi implements LLMApi {
|
|||||||
// if (visionModel && messages.length > 1) {
|
// if (visionModel && messages.length > 1) {
|
||||||
// options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision"));
|
// options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision"));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
...useAppConfig.getState().modelConfig,
|
...useAppConfig.getState().modelConfig,
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||||
@@ -132,30 +86,47 @@ export class GeminiProApi implements LLMApi {
|
|||||||
safetySettings: [
|
safetySettings: [
|
||||||
{
|
{
|
||||||
category: "HARM_CATEGORY_HARASSMENT",
|
category: "HARM_CATEGORY_HARASSMENT",
|
||||||
threshold: accessStore.googleSafetySettings,
|
threshold: "BLOCK_ONLY_HIGH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "HARM_CATEGORY_HATE_SPEECH",
|
category: "HARM_CATEGORY_HATE_SPEECH",
|
||||||
threshold: accessStore.googleSafetySettings,
|
threshold: "BLOCK_ONLY_HIGH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||||
threshold: accessStore.googleSafetySettings,
|
threshold: "BLOCK_ONLY_HIGH",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
|
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||||
threshold: accessStore.googleSafetySettings,
|
threshold: "BLOCK_ONLY_HIGH",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accessStore = useAccessStore.getState();
|
||||||
|
let baseUrl = accessStore.googleUrl;
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
let shouldStream = !!options.config.stream;
|
let shouldStream = !!options.config.stream;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
options.onController?.(controller);
|
options.onController?.(controller);
|
||||||
try {
|
try {
|
||||||
// https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/Streaming_REST.ipynb
|
let googleChatPath = visionModel
|
||||||
const chatPath = this.path(Google.ChatPath(modelConfig.model));
|
? Google.VisionChatPath
|
||||||
|
: Google.ChatPath;
|
||||||
|
let chatPath = this.path(googleChatPath);
|
||||||
|
|
||||||
|
// let baseUrl = accessStore.googleUrl;
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
baseUrl = isApp
|
||||||
|
? DEFAULT_API_HOST + "/api/proxy/google/" + googleChatPath
|
||||||
|
: chatPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isApp) {
|
||||||
|
baseUrl += `?key=${accessStore.googleApiKey}`;
|
||||||
|
}
|
||||||
const chatPayload = {
|
const chatPayload = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(requestPayload),
|
body: JSON.stringify(requestPayload),
|
||||||
@@ -168,17 +139,15 @@ export class GeminiProApi implements LLMApi {
|
|||||||
() => controller.abort(),
|
() => controller.abort(),
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldStream) {
|
if (shouldStream) {
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
let remainText = "";
|
let remainText = "";
|
||||||
let finished = false;
|
let finished = false;
|
||||||
|
|
||||||
|
let existingTexts: string[] = [];
|
||||||
const finish = () => {
|
const finish = () => {
|
||||||
if (!finished) {
|
finished = true;
|
||||||
finished = true;
|
options.onFinish(existingTexts.join(""));
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
// animate response to make it looks smooth
|
||||||
@@ -203,83 +172,74 @@ export class GeminiProApi implements LLMApi {
|
|||||||
// start animaion
|
// start animaion
|
||||||
animateResponseText();
|
animateResponseText();
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
fetch(
|
||||||
|
baseUrl.replace("generateContent", "streamGenerateContent"),
|
||||||
|
chatPayload,
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
const reader = response?.body?.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let partialData = "";
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
return reader?.read().then(function processText({
|
||||||
...chatPayload,
|
done,
|
||||||
async onopen(res) {
|
value,
|
||||||
clearTimeout(requestTimeoutId);
|
}): Promise<any> {
|
||||||
const contentType = res.headers.get("content-type");
|
if (done) {
|
||||||
console.log(
|
if (response.status !== 200) {
|
||||||
"[Gemini] request response content type: ",
|
try {
|
||||||
contentType,
|
let data = JSON.parse(ensureProperEnding(partialData));
|
||||||
);
|
if (data && data[0].error) {
|
||||||
|
options.onError?.(new Error(data[0].error.message));
|
||||||
|
} else {
|
||||||
|
options.onError?.(new Error("Request failed"));
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
options.onError?.(new Error("Request failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
console.log("Stream complete");
|
||||||
responseText = await res.clone().text();
|
// options.onFinish(responseText + remainText);
|
||||||
return finish();
|
finished = true;
|
||||||
}
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
partialData += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
try {
|
||||||
const resJson = await res.clone().json();
|
let data = JSON.parse(ensureProperEnding(partialData));
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
const textArray = data.reduce(
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
(acc: string[], item: { candidates: any[] }) => {
|
||||||
|
const texts = item.candidates.map((candidate) =>
|
||||||
|
candidate.content.parts
|
||||||
|
.map((part: { text: any }) => part.text)
|
||||||
|
.join(""),
|
||||||
|
);
|
||||||
|
return acc.concat(texts);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (textArray.length > existingTexts.length) {
|
||||||
|
const deltaArray = textArray.slice(existingTexts.length);
|
||||||
|
existingTexts = textArray;
|
||||||
|
remainText += deltaArray.join("");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.log("[Response Animation] error: ", error,partialData);
|
||||||
|
// skip error message when parsing json
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extraInfo) {
|
return reader.read().then(processText);
|
||||||
responseTexts.push(extraInfo);
|
});
|
||||||
}
|
})
|
||||||
|
.catch((error) => {
|
||||||
responseText = responseTexts.join("\n\n");
|
console.error("Error:", error);
|
||||||
|
});
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const delta = apiClient.extractMessage(json);
|
|
||||||
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockReason = json?.promptFeedback?.blockReason;
|
|
||||||
if (blockReason) {
|
|
||||||
// being blocked
|
|
||||||
console.log(`[Google] [Safety Ratings] result:`, blockReason);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const res = await fetch(chatPath, chatPayload);
|
const res = await fetch(baseUrl, chatPayload);
|
||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
if (resJson?.promptFeedback?.blockReason) {
|
if (resJson?.promptFeedback?.blockReason) {
|
||||||
@@ -291,7 +251,7 @@ export class GeminiProApi implements LLMApi {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const message = apiClient.extractMessage(resJson);
|
const message = this.extractMessage(resJson);
|
||||||
options.onFinish(message);
|
options.onFinish(message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -305,4 +265,14 @@ export class GeminiProApi implements LLMApi {
|
|||||||
async models(): Promise<LLMModel[]> {
|
async models(): Promise<LLMModel[]> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
path(path: string): string {
|
||||||
|
return "/api/google/" + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureProperEnding(str: string) {
|
||||||
|
if (str.startsWith("[") && !str.endsWith("]")) {
|
||||||
|
return str + "]";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import {
|
|
||||||
ApiPath,
|
|
||||||
DEFAULT_API_HOST,
|
|
||||||
Iflytek,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
|
|
||||||
import { ChatOptions, getHeaders, LLMApi, LLMModel } from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
|
||||||
|
|
||||||
import { OpenAIListModelResponse, RequestPayload } from "./openai";
|
|
||||||
|
|
||||||
export class SparkApi implements LLMApi {
|
|
||||||
private disableListModels = true;
|
|
||||||
|
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.iflytekUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
const apiPath = ApiPath.Iflytek;
|
|
||||||
baseUrl = isApp ? DEFAULT_API_HOST + "/proxy" + apiPath : apiPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Iflytek)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
return [baseUrl, path].join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
extractMessage(res: any) {
|
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const messages: ChatOptions["messages"] = [];
|
|
||||||
for (const v of options.messages) {
|
|
||||||
const content = getMessageTextContent(v);
|
|
||||||
messages.push({ role: v.role, content });
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
providerName: options.config.providerName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestPayload: RequestPayload = {
|
|
||||||
messages,
|
|
||||||
stream: options.config.stream,
|
|
||||||
model: modelConfig.model,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
presence_penalty: modelConfig.presence_penalty,
|
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
|
||||||
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("[Request] Spark payload: ", requestPayload);
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const chatPath = this.path(Iflytek.ChatPath);
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// Animate response text to make it look smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start animation
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log("[Spark] request response content type: ", contentType);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different error scenarios
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
extraInfo = Locale.Error.Unauthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.onError?.(
|
|
||||||
new Error(
|
|
||||||
`Request failed with status ${res.status}: ${extraInfo}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const choices = json.choices as Array<{
|
|
||||||
delta: { content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.delta?.content;
|
|
||||||
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text);
|
|
||||||
options.onError?.(new Error(`Failed to parse response: ${text}`));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const errorText = await res.text();
|
|
||||||
options.onError?.(
|
|
||||||
new Error(`Request failed with status ${res.status}: ${errorText}`),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = this.extractMessage(resJson);
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
"use client";
|
|
||||||
// azure and openai, using same models. so using same LLMApi.
|
|
||||||
import {
|
|
||||||
ApiPath,
|
|
||||||
DEFAULT_API_HOST,
|
|
||||||
DEFAULT_MODELS,
|
|
||||||
Moonshot,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
ServiceProvider,
|
|
||||||
} from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
import { collectModelsWithDefaultModel } from "@/app/utils/model";
|
|
||||||
import { preProcessImageContent } from "@/app/utils/chat";
|
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChatOptions,
|
|
||||||
getHeaders,
|
|
||||||
LLMApi,
|
|
||||||
LLMModel,
|
|
||||||
LLMUsage,
|
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent } from "@/app/utils";
|
|
||||||
|
|
||||||
import { OpenAIListModelResponse, RequestPayload } from "./openai";
|
|
||||||
|
|
||||||
export class MoonshotApi implements LLMApi {
|
|
||||||
private disableListModels = true;
|
|
||||||
|
|
||||||
path(path: string): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.moonshotUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
const apiPath = ApiPath.Moonshot;
|
|
||||||
baseUrl = isApp ? DEFAULT_API_HOST + "/proxy" + apiPath : apiPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Moonshot)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
|
||||||
|
|
||||||
return [baseUrl, path].join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
extractMessage(res: any) {
|
|
||||||
return res.choices?.at(0)?.message?.content ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const messages: ChatOptions["messages"] = [];
|
|
||||||
for (const v of options.messages) {
|
|
||||||
const content = getMessageTextContent(v);
|
|
||||||
messages.push({ role: v.role, content });
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
providerName: options.config.providerName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestPayload: RequestPayload = {
|
|
||||||
messages,
|
|
||||||
stream: options.config.stream,
|
|
||||||
model: modelConfig.model,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
presence_penalty: modelConfig.presence_penalty,
|
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
|
||||||
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("[Request] openai payload: ", requestPayload);
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const chatPath = this.path(Moonshot.ChatPath);
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
if (responseText?.length === 0) {
|
|
||||||
options.onError?.(new Error("empty response from server"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animaion
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log(
|
|
||||||
"[OpenAI] request response content type: ",
|
|
||||||
contentType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
responseTexts.push(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
responseText = responseTexts.join("\n\n");
|
|
||||||
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const choices = json.choices as Array<{
|
|
||||||
delta: { content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.delta?.content;
|
|
||||||
const textmoderation = json?.prompt_filter_results;
|
|
||||||
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = this.extractMessage(resJson);
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
// azure and openai, using same models. so using same LLMApi.
|
|
||||||
import {
|
import {
|
||||||
ApiPath,
|
ApiPath,
|
||||||
DEFAULT_API_HOST,
|
DEFAULT_API_HOST,
|
||||||
DEFAULT_MODELS,
|
DEFAULT_MODELS,
|
||||||
OpenaiPath,
|
OpenaiPath,
|
||||||
Azure,
|
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
ServiceProvider,
|
ServiceProvider,
|
||||||
} from "@/app/constant";
|
} from "@/app/constant";
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||||
import { collectModelsWithDefaultModel } from "@/app/utils/model";
|
|
||||||
import {
|
|
||||||
preProcessImageContent,
|
|
||||||
uploadImage,
|
|
||||||
base64Image2Blob,
|
|
||||||
} from "@/app/utils/chat";
|
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
|
||||||
import { DalleSize, DalleQuality, DalleStyle } from "@/app/typing";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChatOptions,
|
ChatOptions,
|
||||||
@@ -34,11 +24,11 @@ import {
|
|||||||
} from "@fortaine/fetch-event-source";
|
} from "@fortaine/fetch-event-source";
|
||||||
import { prettyObject } from "@/app/utils/format";
|
import { prettyObject } from "@/app/utils/format";
|
||||||
import { getClientConfig } from "@/app/config/client";
|
import { getClientConfig } from "@/app/config/client";
|
||||||
|
import { makeAzurePath } from "@/app/azure";
|
||||||
import {
|
import {
|
||||||
getMessageTextContent,
|
getMessageTextContent,
|
||||||
getMessageImages,
|
getMessageImages,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
isDalle3 as _isDalle3,
|
|
||||||
} from "@/app/utils";
|
} from "@/app/utils";
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
export interface OpenAIListModelResponse {
|
||||||
@@ -50,189 +40,94 @@ export interface OpenAIListModelResponse {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestPayload {
|
|
||||||
messages: {
|
|
||||||
role: "system" | "user" | "assistant";
|
|
||||||
content: string | MultimodalContent[];
|
|
||||||
}[];
|
|
||||||
stream?: boolean;
|
|
||||||
model: string;
|
|
||||||
temperature: number;
|
|
||||||
presence_penalty: number;
|
|
||||||
frequency_penalty: number;
|
|
||||||
top_p: number;
|
|
||||||
max_tokens?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DalleRequestPayload {
|
|
||||||
model: string;
|
|
||||||
prompt: string;
|
|
||||||
response_format: "url" | "b64_json";
|
|
||||||
n: number;
|
|
||||||
size: DalleSize;
|
|
||||||
quality: DalleQuality;
|
|
||||||
style: DalleStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatGPTApi implements LLMApi {
|
export class ChatGPTApi implements LLMApi {
|
||||||
private disableListModels = true;
|
private disableListModels = true;
|
||||||
|
|
||||||
path(path: string): string {
|
path(path: string): string {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
|
|
||||||
let baseUrl = "";
|
const isAzure = accessStore.provider === ServiceProvider.Azure;
|
||||||
|
|
||||||
const isAzure = path.includes("deployments");
|
if (isAzure && !accessStore.isValidAzure()) {
|
||||||
if (accessStore.useCustomConfig) {
|
throw Error(
|
||||||
if (isAzure && !accessStore.isValidAzure()) {
|
"incomplete azure config, please check it in your settings page",
|
||||||
throw Error(
|
);
|
||||||
"incomplete azure config, please check it in your settings page",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
if (baseUrl.length === 0) {
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
const apiPath = isAzure ? ApiPath.Azure : ApiPath.OpenAI;
|
baseUrl = isApp
|
||||||
baseUrl = isApp ? DEFAULT_API_HOST + "/proxy" + apiPath : apiPath;
|
? DEFAULT_API_HOST + "/proxy" + ApiPath.OpenAI
|
||||||
|
: ApiPath.OpenAI;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
if (baseUrl.endsWith("/")) {
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
||||||
}
|
}
|
||||||
if (
|
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
|
||||||
!baseUrl.startsWith("http") &&
|
|
||||||
!isAzure &&
|
|
||||||
!baseUrl.startsWith(ApiPath.OpenAI)
|
|
||||||
) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
baseUrl = "https://" + baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAzure) {
|
||||||
|
path = makeAzurePath(path, accessStore.azureApiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl, path);
|
console.log("[Proxy Endpoint] ", baseUrl, path);
|
||||||
|
|
||||||
// try rebuild url, when using cloudflare ai gateway in client
|
return [baseUrl, path].join("/");
|
||||||
return cloudflareAIGatewayUrl([baseUrl, path].join("/"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async extractMessage(res: any) {
|
extractMessage(res: any) {
|
||||||
if (res.error) {
|
return res.choices?.at(0)?.message?.content ?? "";
|
||||||
return "```\n" + JSON.stringify(res, null, 4) + "\n```";
|
|
||||||
}
|
|
||||||
// dalle3 model return url, using url create image message
|
|
||||||
if (res.data) {
|
|
||||||
let url = res.data?.at(0)?.url ?? "";
|
|
||||||
const b64_json = res.data?.at(0)?.b64_json ?? "";
|
|
||||||
if (!url && b64_json) {
|
|
||||||
// uploadImage
|
|
||||||
url = await uploadImage(base64Image2Blob(b64_json, "image/png"));
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: "image_url",
|
|
||||||
image_url: {
|
|
||||||
url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return res.choices?.at(0)?.message?.content ?? res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
async chat(options: ChatOptions) {
|
||||||
|
const visionModel = isVisionModel(options.config.model);
|
||||||
|
const messages = options.messages.map((v) => ({
|
||||||
|
role: v.role,
|
||||||
|
content: visionModel ? v.content : getMessageTextContent(v),
|
||||||
|
}));
|
||||||
|
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
...useAppConfig.getState().modelConfig,
|
...useAppConfig.getState().modelConfig,
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||||
...{
|
...{
|
||||||
model: options.config.model,
|
model: options.config.model,
|
||||||
providerName: options.config.providerName,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let requestPayload: RequestPayload | DalleRequestPayload;
|
const requestPayload = {
|
||||||
|
messages,
|
||||||
|
stream: options.config.stream,
|
||||||
|
model: modelConfig.model,
|
||||||
|
temperature: modelConfig.temperature,
|
||||||
|
presence_penalty: modelConfig.presence_penalty,
|
||||||
|
frequency_penalty: modelConfig.frequency_penalty,
|
||||||
|
top_p: modelConfig.top_p,
|
||||||
|
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
||||||
|
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
|
||||||
|
};
|
||||||
|
|
||||||
const isDalle3 = _isDalle3(options.config.model);
|
// add max_tokens to vision model
|
||||||
if (isDalle3) {
|
if (visionModel) {
|
||||||
const prompt = getMessageTextContent(
|
Object.defineProperty(requestPayload, "max_tokens", {
|
||||||
options.messages.slice(-1)?.pop() as any,
|
enumerable: true,
|
||||||
);
|
configurable: true,
|
||||||
requestPayload = {
|
writable: true,
|
||||||
model: options.config.model,
|
value: modelConfig.max_tokens,
|
||||||
prompt,
|
});
|
||||||
// URLs are only valid for 60 minutes after the image has been generated.
|
|
||||||
response_format: "b64_json", // using b64_json, and save image in CacheStorage
|
|
||||||
n: 1,
|
|
||||||
size: options.config?.size ?? "1024x1024",
|
|
||||||
quality: options.config?.quality ?? "standard",
|
|
||||||
style: options.config?.style ?? "vivid",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const visionModel = isVisionModel(options.config.model);
|
|
||||||
const messages: ChatOptions["messages"] = [];
|
|
||||||
for (const v of options.messages) {
|
|
||||||
const content = visionModel
|
|
||||||
? await preProcessImageContent(v.content)
|
|
||||||
: getMessageTextContent(v);
|
|
||||||
messages.push({ role: v.role, content });
|
|
||||||
}
|
|
||||||
|
|
||||||
requestPayload = {
|
|
||||||
messages,
|
|
||||||
stream: options.config.stream,
|
|
||||||
model: modelConfig.model,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
presence_penalty: modelConfig.presence_penalty,
|
|
||||||
frequency_penalty: modelConfig.frequency_penalty,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
|
||||||
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
|
|
||||||
};
|
|
||||||
|
|
||||||
// add max_tokens to vision model
|
|
||||||
if (visionModel && modelConfig.model.includes("preview")) {
|
|
||||||
requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[Request] openai payload: ", requestPayload);
|
console.log("[Request] openai payload: ", requestPayload);
|
||||||
|
|
||||||
const shouldStream = !isDalle3 && !!options.config.stream;
|
const shouldStream = !!options.config.stream;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
options.onController?.(controller);
|
options.onController?.(controller);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let chatPath = "";
|
const chatPath = this.path(OpenaiPath.ChatPath);
|
||||||
if (modelConfig.providerName === ServiceProvider.Azure) {
|
|
||||||
// find model, and get displayName as deployName
|
|
||||||
const { models: configModels, customModels: configCustomModels } =
|
|
||||||
useAppConfig.getState();
|
|
||||||
const {
|
|
||||||
defaultModel,
|
|
||||||
customModels: accessCustomModels,
|
|
||||||
useCustomConfig,
|
|
||||||
} = useAccessStore.getState();
|
|
||||||
const models = collectModelsWithDefaultModel(
|
|
||||||
configModels,
|
|
||||||
[configCustomModels, accessCustomModels].join(","),
|
|
||||||
defaultModel,
|
|
||||||
);
|
|
||||||
const model = models.find(
|
|
||||||
(model) =>
|
|
||||||
model.name === modelConfig.model &&
|
|
||||||
model?.provider?.providerName === ServiceProvider.Azure,
|
|
||||||
);
|
|
||||||
chatPath = this.path(
|
|
||||||
(isDalle3 ? Azure.ImagePath : Azure.ChatPath)(
|
|
||||||
(model?.displayName ?? model?.name) as string,
|
|
||||||
useCustomConfig ? useAccessStore.getState().azureApiVersion : "",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
chatPath = this.path(
|
|
||||||
isDalle3 ? OpenaiPath.ImagePath : OpenaiPath.ChatPath,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const chatPayload = {
|
const chatPayload = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(requestPayload),
|
body: JSON.stringify(requestPayload),
|
||||||
@@ -243,7 +138,7 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
// make a fetch request
|
// make a fetch request
|
||||||
const requestTimeoutId = setTimeout(
|
const requestTimeoutId = setTimeout(
|
||||||
() => controller.abort(),
|
() => controller.abort(),
|
||||||
isDalle3 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
|
REQUEST_TIMEOUT_MS,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldStream) {
|
if (shouldStream) {
|
||||||
@@ -334,9 +229,7 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
const text = msg.data;
|
const text = msg.data;
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(text);
|
const json = JSON.parse(text);
|
||||||
const choices = json.choices as Array<{
|
const choices = json.choices as Array<{ delta: { content: string } }>;
|
||||||
delta: { content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.delta?.content;
|
const delta = choices[0]?.delta?.content;
|
||||||
const textmoderation = json?.prompt_filter_results;
|
const textmoderation = json?.prompt_filter_results;
|
||||||
|
|
||||||
@@ -344,17 +237,9 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
remainText += delta;
|
remainText += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (textmoderation && textmoderation.length > 0 && ServiceProvider.Azure) {
|
||||||
textmoderation &&
|
const contentFilterResults = textmoderation[0]?.content_filter_results;
|
||||||
textmoderation.length > 0 &&
|
console.log(`[${ServiceProvider.Azure}] [Text Moderation] flagged categories result:`, contentFilterResults);
|
||||||
ServiceProvider.Azure
|
|
||||||
) {
|
|
||||||
const contentFilterResults =
|
|
||||||
textmoderation[0]?.content_filter_results;
|
|
||||||
console.log(
|
|
||||||
`[${ServiceProvider.Azure}] [Text Moderation] flagged categories result:`,
|
|
||||||
contentFilterResults,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Request] parse error", text, msg);
|
console.error("[Request] parse error", text, msg);
|
||||||
@@ -374,7 +259,7 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
|
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
const message = await this.extractMessage(resJson);
|
const message = this.extractMessage(resJson);
|
||||||
options.onFinish(message);
|
options.onFinish(message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -468,17 +353,13 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
//由于目前 OpenAI 的 disableListModels 默认为 true,所以当前实际不会运行到这场
|
|
||||||
let seq = 1000; //同 Constant.ts 中的排序保持一致
|
|
||||||
return chatModels.map((m) => ({
|
return chatModels.map((m) => ({
|
||||||
name: m.id,
|
name: m.id,
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "openai",
|
id: "openai",
|
||||||
providerName: "OpenAI",
|
providerName: "OpenAI",
|
||||||
providerType: "openai",
|
providerType: "openai",
|
||||||
sorted: 1,
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,268 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { ApiPath, DEFAULT_API_HOST, REQUEST_TIMEOUT_MS } from "@/app/constant";
|
|
||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChatOptions,
|
|
||||||
getHeaders,
|
|
||||||
LLMApi,
|
|
||||||
LLMModel,
|
|
||||||
MultimodalContent,
|
|
||||||
} from "../api";
|
|
||||||
import Locale from "../../locales";
|
|
||||||
import {
|
|
||||||
EventStreamContentType,
|
|
||||||
fetchEventSource,
|
|
||||||
} from "@fortaine/fetch-event-source";
|
|
||||||
import { prettyObject } from "@/app/utils/format";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { getMessageTextContent, isVisionModel } from "@/app/utils";
|
|
||||||
import mapKeys from "lodash-es/mapKeys";
|
|
||||||
import mapValues from "lodash-es/mapValues";
|
|
||||||
import isArray from "lodash-es/isArray";
|
|
||||||
import isObject from "lodash-es/isObject";
|
|
||||||
|
|
||||||
export interface OpenAIListModelResponse {
|
|
||||||
object: string;
|
|
||||||
data: Array<{
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
root: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestPayload {
|
|
||||||
Messages: {
|
|
||||||
Role: "system" | "user" | "assistant";
|
|
||||||
Content: string | MultimodalContent[];
|
|
||||||
}[];
|
|
||||||
Stream?: boolean;
|
|
||||||
Model: string;
|
|
||||||
Temperature: number;
|
|
||||||
TopP: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function capitalizeKeys(obj: any): any {
|
|
||||||
if (isArray(obj)) {
|
|
||||||
return obj.map(capitalizeKeys);
|
|
||||||
} else if (isObject(obj)) {
|
|
||||||
return mapValues(
|
|
||||||
mapKeys(obj, (value: any, key: string) =>
|
|
||||||
key.replace(/(^|_)(\w)/g, (m, $1, $2) => $2.toUpperCase()),
|
|
||||||
),
|
|
||||||
capitalizeKeys,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HunyuanApi implements LLMApi {
|
|
||||||
path(): string {
|
|
||||||
const accessStore = useAccessStore.getState();
|
|
||||||
|
|
||||||
let baseUrl = "";
|
|
||||||
|
|
||||||
if (accessStore.useCustomConfig) {
|
|
||||||
baseUrl = accessStore.tencentUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.length === 0) {
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
baseUrl = isApp
|
|
||||||
? DEFAULT_API_HOST + "/api/proxy/tencent"
|
|
||||||
: ApiPath.Tencent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
|
|
||||||
}
|
|
||||||
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Tencent)) {
|
|
||||||
baseUrl = "https://" + baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[Proxy Endpoint] ", baseUrl);
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractMessage(res: any) {
|
|
||||||
return res.Choices?.at(0)?.Message?.Content ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async chat(options: ChatOptions) {
|
|
||||||
const visionModel = isVisionModel(options.config.model);
|
|
||||||
const messages = options.messages.map((v, index) => ({
|
|
||||||
// "Messages 中 system 角色必须位于列表的最开始"
|
|
||||||
role: index !== 0 && v.role === "system" ? "user" : v.role,
|
|
||||||
content: visionModel ? v.content : getMessageTextContent(v),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const modelConfig = {
|
|
||||||
...useAppConfig.getState().modelConfig,
|
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
|
||||||
...{
|
|
||||||
model: options.config.model,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestPayload: RequestPayload = capitalizeKeys({
|
|
||||||
model: modelConfig.model,
|
|
||||||
messages,
|
|
||||||
temperature: modelConfig.temperature,
|
|
||||||
top_p: modelConfig.top_p,
|
|
||||||
stream: options.config.stream,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[Request] Tencent payload: ", requestPayload);
|
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
|
||||||
const controller = new AbortController();
|
|
||||||
options.onController?.(controller);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const chatPath = this.path();
|
|
||||||
const chatPayload = {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
signal: controller.signal,
|
|
||||||
headers: getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// make a fetch request
|
|
||||||
const requestTimeoutId = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
REQUEST_TIMEOUT_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldStream) {
|
|
||||||
let responseText = "";
|
|
||||||
let remainText = "";
|
|
||||||
let finished = false;
|
|
||||||
|
|
||||||
// animate response to make it looks smooth
|
|
||||||
function animateResponseText() {
|
|
||||||
if (finished || controller.signal.aborted) {
|
|
||||||
responseText += remainText;
|
|
||||||
console.log("[Response Animation] finished");
|
|
||||||
if (responseText?.length === 0) {
|
|
||||||
options.onError?.(new Error("empty response from server"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainText.length > 0) {
|
|
||||||
const fetchCount = Math.max(1, Math.round(remainText.length / 60));
|
|
||||||
const fetchText = remainText.slice(0, fetchCount);
|
|
||||||
responseText += fetchText;
|
|
||||||
remainText = remainText.slice(fetchCount);
|
|
||||||
options.onUpdate?.(responseText, fetchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateResponseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start animaion
|
|
||||||
animateResponseText();
|
|
||||||
|
|
||||||
const finish = () => {
|
|
||||||
if (!finished) {
|
|
||||||
finished = true;
|
|
||||||
options.onFinish(responseText + remainText);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.signal.onabort = finish;
|
|
||||||
|
|
||||||
fetchEventSource(chatPath, {
|
|
||||||
...chatPayload,
|
|
||||||
async onopen(res) {
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
const contentType = res.headers.get("content-type");
|
|
||||||
console.log(
|
|
||||||
"[Tencent] request response content type: ",
|
|
||||||
contentType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
|
||||||
responseText = await res.clone().text();
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!res.ok ||
|
|
||||||
!res.headers
|
|
||||||
.get("content-type")
|
|
||||||
?.startsWith(EventStreamContentType) ||
|
|
||||||
res.status !== 200
|
|
||||||
) {
|
|
||||||
const responseTexts = [responseText];
|
|
||||||
let extraInfo = await res.clone().text();
|
|
||||||
try {
|
|
||||||
const resJson = await res.clone().json();
|
|
||||||
extraInfo = prettyObject(resJson);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
responseTexts.push(Locale.Error.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
responseTexts.push(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
responseText = responseTexts.join("\n\n");
|
|
||||||
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmessage(msg) {
|
|
||||||
if (msg.data === "[DONE]" || finished) {
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
const text = msg.data;
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
const choices = json.Choices as Array<{
|
|
||||||
Delta: { Content: string };
|
|
||||||
}>;
|
|
||||||
const delta = choices[0]?.Delta?.Content;
|
|
||||||
if (delta) {
|
|
||||||
remainText += delta;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Request] parse error", text, msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onclose() {
|
|
||||||
finish();
|
|
||||||
},
|
|
||||||
onerror(e) {
|
|
||||||
options.onError?.(e);
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
openWhenHidden: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
|
||||||
clearTimeout(requestTimeoutId);
|
|
||||||
|
|
||||||
const resJson = await res.json();
|
|
||||||
const message = this.extractMessage(resJson);
|
|
||||||
options.onFinish(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[Request] failed to make a chat request", e);
|
|
||||||
options.onError?.(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async usage() {
|
|
||||||
return {
|
|
||||||
used: 0,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async models(): Promise<LLMModel[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,16 +41,13 @@ interface ChatCommands {
|
|||||||
del?: Command;
|
del?: Command;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compatible with Chinese colon character ":"
|
export const ChatCommandPrefix = ":";
|
||||||
export const ChatCommandPrefix = /^[::]/;
|
|
||||||
|
|
||||||
export function useChatCommand(commands: ChatCommands = {}) {
|
export function useChatCommand(commands: ChatCommands = {}) {
|
||||||
function extract(userInput: string) {
|
function extract(userInput: string) {
|
||||||
const match = userInput.match(ChatCommandPrefix);
|
return (
|
||||||
if (match) {
|
userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput
|
||||||
return userInput.slice(1) as keyof ChatCommands;
|
) as keyof ChatCommands;
|
||||||
}
|
|
||||||
return userInput as keyof ChatCommands;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function search(userInput: string) {
|
function search(userInput: string) {
|
||||||
@@ -60,7 +57,7 @@ export function useChatCommand(commands: ChatCommands = {}) {
|
|||||||
.filter((c) => c.startsWith(input))
|
.filter((c) => c.startsWith(input))
|
||||||
.map((c) => ({
|
.map((c) => ({
|
||||||
title: desc[c as keyof ChatCommands],
|
title: desc[c as keyof ChatCommands],
|
||||||
content: ":" + c,
|
content: ChatCommandPrefix + c,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
.artifacts {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
&-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 36px;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--second);
|
|
||||||
}
|
|
||||||
&-title {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
&-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 0 20px 20px 20px;
|
|
||||||
background-color: var(--second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.artifacts-iframe {
|
|
||||||
width: 100%;
|
|
||||||
border: var(--border-in-light);
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: var(--gray);
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import { useEffect, useState, useRef, useMemo } from "react";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
import { useWindowSize } from "@/app/utils";
|
|
||||||
import { IconButton } from "./button";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import ExportIcon from "../icons/share.svg";
|
|
||||||
import CopyIcon from "../icons/copy.svg";
|
|
||||||
import DownloadIcon from "../icons/download.svg";
|
|
||||||
import GithubIcon from "../icons/github.svg";
|
|
||||||
import LoadingButtonIcon from "../icons/loading.svg";
|
|
||||||
import Locale from "../locales";
|
|
||||||
import { Modal, showToast } from "./ui-lib";
|
|
||||||
import { copyToClipboard, downloadAs } from "../utils";
|
|
||||||
import { Path, ApiPath, REPO_URL } from "@/app/constant";
|
|
||||||
import { Loading } from "./home";
|
|
||||||
import styles from "./artifacts.module.scss";
|
|
||||||
|
|
||||||
export function HTMLPreview(props: {
|
|
||||||
code: string;
|
|
||||||
autoHeight?: boolean;
|
|
||||||
height?: number | string;
|
|
||||||
onLoad?: (title?: string) => void;
|
|
||||||
}) {
|
|
||||||
const ref = useRef<HTMLIFrameElement>(null);
|
|
||||||
const frameId = useRef<string>(nanoid());
|
|
||||||
const [iframeHeight, setIframeHeight] = useState(600);
|
|
||||||
const [title, setTitle] = useState("");
|
|
||||||
/*
|
|
||||||
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
|
|
||||||
* 1. using srcdoc
|
|
||||||
* 2. using src with dataurl:
|
|
||||||
* easy to share
|
|
||||||
* length limit (Data URIs cannot be larger than 32,768 characters.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMessage = (e: any) => {
|
|
||||||
const { id, height, title } = e.data;
|
|
||||||
setTitle(title);
|
|
||||||
if (id == frameId.current) {
|
|
||||||
setIframeHeight(height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener("message", handleMessage);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("message", handleMessage);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const height = useMemo(() => {
|
|
||||||
if (!props.autoHeight) return props.height || 600;
|
|
||||||
if (typeof props.height === "string") {
|
|
||||||
return props.height;
|
|
||||||
}
|
|
||||||
const parentHeight = props.height || 600;
|
|
||||||
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
|
|
||||||
}, [props.autoHeight, props.height, iframeHeight]);
|
|
||||||
|
|
||||||
const srcDoc = useMemo(() => {
|
|
||||||
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
|
||||||
if (props.code.includes("</head>")) {
|
|
||||||
props.code.replace("</head>", "</head>" + script);
|
|
||||||
}
|
|
||||||
return props.code + script;
|
|
||||||
}, [props.code]);
|
|
||||||
|
|
||||||
const handleOnLoad = () => {
|
|
||||||
if (props?.onLoad) {
|
|
||||||
props.onLoad(title);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
className={styles["artifacts-iframe"]}
|
|
||||||
id={frameId.current}
|
|
||||||
ref={ref}
|
|
||||||
sandbox="allow-forms allow-modals allow-scripts"
|
|
||||||
style={{ height }}
|
|
||||||
srcDoc={srcDoc}
|
|
||||||
onLoad={handleOnLoad}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArtifactsShareButton({
|
|
||||||
getCode,
|
|
||||||
id,
|
|
||||||
style,
|
|
||||||
fileName,
|
|
||||||
}: {
|
|
||||||
getCode: () => string;
|
|
||||||
id?: string;
|
|
||||||
style?: any;
|
|
||||||
fileName?: string;
|
|
||||||
}) {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [name, setName] = useState(id);
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
const shareUrl = useMemo(
|
|
||||||
() => [location.origin, "#", Path.Artifacts, "/", name].join(""),
|
|
||||||
[name],
|
|
||||||
);
|
|
||||||
const upload = (code: string) =>
|
|
||||||
id
|
|
||||||
? Promise.resolve({ id })
|
|
||||||
: fetch(ApiPath.Artifacts, {
|
|
||||||
method: "POST",
|
|
||||||
body: code,
|
|
||||||
})
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then(({ id }) => {
|
|
||||||
if (id) {
|
|
||||||
return { id };
|
|
||||||
}
|
|
||||||
throw Error();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
showToast(Locale.Export.Artifacts.Error);
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="window-action-button" style={style}>
|
|
||||||
<IconButton
|
|
||||||
icon={loading ? <LoadingButtonIcon /> : <ExportIcon />}
|
|
||||||
bordered
|
|
||||||
title={Locale.Export.Artifacts.Title}
|
|
||||||
onClick={() => {
|
|
||||||
if (loading) return;
|
|
||||||
setLoading(true);
|
|
||||||
upload(getCode())
|
|
||||||
.then((res) => {
|
|
||||||
if (res?.id) {
|
|
||||||
setShow(true);
|
|
||||||
setName(res?.id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{show && (
|
|
||||||
<div className="modal-mask">
|
|
||||||
<Modal
|
|
||||||
title={Locale.Export.Artifacts.Title}
|
|
||||||
onClose={() => setShow(false)}
|
|
||||||
actions={[
|
|
||||||
<IconButton
|
|
||||||
key="download"
|
|
||||||
icon={<DownloadIcon />}
|
|
||||||
bordered
|
|
||||||
text={Locale.Export.Download}
|
|
||||||
onClick={() => {
|
|
||||||
downloadAs(getCode(), `${fileName || name}.html`).then(() =>
|
|
||||||
setShow(false),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
<IconButton
|
|
||||||
key="copy"
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
bordered
|
|
||||||
text={Locale.Chat.Actions.Copy}
|
|
||||||
onClick={() => {
|
|
||||||
copyToClipboard(shareUrl).then(() => setShow(false));
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<a target="_blank" href={shareUrl}>
|
|
||||||
{shareUrl}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Artifacts() {
|
|
||||||
const { id } = useParams();
|
|
||||||
const [code, setCode] = useState("");
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [fileName, setFileName] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
fetch(`${ApiPath.Artifacts}?id=${id}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status > 300) {
|
|
||||||
throw Error("can not get content");
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.then((res) => res.text())
|
|
||||||
.then(setCode)
|
|
||||||
.catch((e) => {
|
|
||||||
showToast(Locale.Export.Artifacts.Error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles["artifacts"]}>
|
|
||||||
<div className={styles["artifacts-header"]}>
|
|
||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
|
||||||
<IconButton bordered icon={<GithubIcon />} shadow />
|
|
||||||
</a>
|
|
||||||
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
|
|
||||||
<ArtifactsShareButton
|
|
||||||
id={id}
|
|
||||||
getCode={() => code}
|
|
||||||
fileName={fileName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles["artifacts-content"]}>
|
|
||||||
{loading && <Loading />}
|
|
||||||
{code && (
|
|
||||||
<HTMLPreview
|
|
||||||
code={code}
|
|
||||||
autoHeight={false}
|
|
||||||
height={"100%"}
|
|
||||||
onLoad={(title) => {
|
|
||||||
setFileName(title as string);
|
|
||||||
setLoading(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,8 @@ export function AuthPage() {
|
|||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
access.openaiApiKey = "";
|
access.openaiApiKey = "";
|
||||||
access.accessCode = "";
|
access.accessCode = "";
|
||||||
|
access.googleApiKey = "";
|
||||||
|
access.anthropicApiKey = "";
|
||||||
});
|
});
|
||||||
}; // Reset access code to empty string
|
}; // Reset access code to empty string
|
||||||
|
|
||||||
@@ -75,6 +77,17 @@ export function AuthPage() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
className={styles["auth-input"]}
|
||||||
|
type="password"
|
||||||
|
placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
|
||||||
|
value={accessStore.anthropicApiKey}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.update(
|
||||||
|
(access) => (access.anthropicApiKey = e.currentTarget.value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import styles from "./button.module.scss";
|
import styles from "./button.module.scss";
|
||||||
import { CSSProperties } from "react";
|
|
||||||
|
|
||||||
export type ButtonType = "primary" | "danger" | null;
|
export type ButtonType = "primary" | "danger" | null;
|
||||||
|
|
||||||
@@ -17,8 +16,6 @@ export function IconButton(props: {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
style?: CSSProperties;
|
|
||||||
aria?: string;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -34,12 +31,9 @@ export function IconButton(props: {
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={props.tabIndex}
|
tabIndex={props.tabIndex}
|
||||||
autoFocus={props.autoFocus}
|
autoFocus={props.autoFocus}
|
||||||
style={props.style}
|
|
||||||
aria-label={props.aria}
|
|
||||||
>
|
>
|
||||||
{props.icon && (
|
{props.icon && (
|
||||||
<div
|
<div
|
||||||
aria-label={props.text || props.title}
|
|
||||||
className={
|
className={
|
||||||
styles["icon-button-icon"] +
|
styles["icon-button-icon"] +
|
||||||
` ${props.type === "primary" && "no-dark"}`
|
` ${props.type === "primary" && "no-dark"}`
|
||||||
@@ -50,12 +44,7 @@ export function IconButton(props: {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{props.text && (
|
{props.text && (
|
||||||
<div
|
<div className={styles["icon-button-text"]}>{props.text}</div>
|
||||||
aria-label={props.text || props.title}
|
|
||||||
className={styles["icon-button-text"]}
|
|
||||||
>
|
|
||||||
{props.text}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -346,12 +346,6 @@
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-model-name {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--black);
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-container {
|
.chat-message-container {
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ import AutoIcon from "../icons/auto.svg";
|
|||||||
import BottomIcon from "../icons/bottom.svg";
|
import BottomIcon from "../icons/bottom.svg";
|
||||||
import StopIcon from "../icons/pause.svg";
|
import StopIcon from "../icons/pause.svg";
|
||||||
import RobotIcon from "../icons/robot.svg";
|
import RobotIcon from "../icons/robot.svg";
|
||||||
import SizeIcon from "../icons/size.svg";
|
|
||||||
import QualityIcon from "../icons/hd.svg";
|
|
||||||
import StyleIcon from "../icons/palette.svg";
|
|
||||||
import PluginIcon from "../icons/plugin.svg";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
@@ -63,15 +59,12 @@ import {
|
|||||||
getMessageTextContent,
|
getMessageTextContent,
|
||||||
getMessageImages,
|
getMessageImages,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
isDalle3,
|
compressImage,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
|
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
import { ChatControllerPool } from "../client/controller";
|
import { ChatControllerPool } from "../client/controller";
|
||||||
import { DalleSize, DalleQuality, DalleStyle } from "../typing";
|
|
||||||
import { Prompt, usePromptStore } from "../store/prompt";
|
import { Prompt, usePromptStore } from "../store/prompt";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
|
||||||
@@ -94,8 +87,6 @@ import {
|
|||||||
Path,
|
Path,
|
||||||
REQUEST_TIMEOUT_MS,
|
REQUEST_TIMEOUT_MS,
|
||||||
UNFINISHED_INPUT,
|
UNFINISHED_INPUT,
|
||||||
ServiceProvider,
|
|
||||||
Plugin,
|
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { Avatar } from "./emoji";
|
import { Avatar } from "./emoji";
|
||||||
import { ContextPrompts, MaskAvatar, MaskConfig } from "./mask";
|
import { ContextPrompts, MaskAvatar, MaskConfig } from "./mask";
|
||||||
@@ -252,11 +243,11 @@ function useSubmitHandler() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderPrompt = Pick<Prompt, "title" | "content">;
|
export type RenderPompt = Pick<Prompt, "title" | "content">;
|
||||||
|
|
||||||
export function PromptHints(props: {
|
export function PromptHints(props: {
|
||||||
prompts: RenderPrompt[];
|
prompts: RenderPompt[];
|
||||||
onPromptSelect: (prompt: RenderPrompt) => void;
|
onPromptSelect: (prompt: RenderPompt) => void;
|
||||||
}) {
|
}) {
|
||||||
const noPrompts = props.prompts.length === 0;
|
const noPrompts = props.prompts.length === 0;
|
||||||
const [selectIndex, setSelectIndex] = useState(0);
|
const [selectIndex, setSelectIndex] = useState(0);
|
||||||
@@ -345,7 +336,7 @@ function ClearContextDivider() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatAction(props: {
|
function ChatAction(props: {
|
||||||
text: string;
|
text: string;
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -456,49 +447,14 @@ export function ChatActions(props: {
|
|||||||
|
|
||||||
// switch model
|
// switch model
|
||||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||||
const currentProviderName =
|
|
||||||
chatStore.currentSession().mask.modelConfig?.providerName ||
|
|
||||||
ServiceProvider.OpenAI;
|
|
||||||
const allModels = useAllModels();
|
const allModels = useAllModels();
|
||||||
const models = useMemo(() => {
|
const models = useMemo(
|
||||||
const filteredModels = allModels.filter((m) => m.available);
|
() => allModels.filter((m) => m.available),
|
||||||
const defaultModel = filteredModels.find((m) => m.isDefault);
|
[allModels],
|
||||||
|
);
|
||||||
if (defaultModel) {
|
|
||||||
const arr = [
|
|
||||||
defaultModel,
|
|
||||||
...filteredModels.filter((m) => m !== defaultModel),
|
|
||||||
];
|
|
||||||
return arr;
|
|
||||||
} else {
|
|
||||||
return filteredModels;
|
|
||||||
}
|
|
||||||
}, [allModels]);
|
|
||||||
const currentModelName = useMemo(() => {
|
|
||||||
const model = models.find(
|
|
||||||
(m) =>
|
|
||||||
m.name == currentModel &&
|
|
||||||
m?.provider?.providerName == currentProviderName,
|
|
||||||
);
|
|
||||||
return model?.displayName ?? "";
|
|
||||||
}, [models, currentModel, currentProviderName]);
|
|
||||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||||
const [showPluginSelector, setShowPluginSelector] = useState(false);
|
|
||||||
const [showUploadImage, setShowUploadImage] = useState(false);
|
const [showUploadImage, setShowUploadImage] = useState(false);
|
||||||
|
|
||||||
const [showSizeSelector, setShowSizeSelector] = useState(false);
|
|
||||||
const [showQualitySelector, setShowQualitySelector] = useState(false);
|
|
||||||
const [showStyleSelector, setShowStyleSelector] = useState(false);
|
|
||||||
const dalle3Sizes: DalleSize[] = ["1024x1024", "1792x1024", "1024x1792"];
|
|
||||||
const dalle3Qualitys: DalleQuality[] = ["standard", "hd"];
|
|
||||||
const dalle3Styles: DalleStyle[] = ["vivid", "natural"];
|
|
||||||
const currentSize =
|
|
||||||
chatStore.currentSession().mask.modelConfig?.size ?? "1024x1024";
|
|
||||||
const currentQuality =
|
|
||||||
chatStore.currentSession().mask.modelConfig?.quality ?? "standard";
|
|
||||||
const currentStyle =
|
|
||||||
chatStore.currentSession().mask.modelConfig?.style ?? "vivid";
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const show = isVisionModel(currentModel);
|
const show = isVisionModel(currentModel);
|
||||||
setShowUploadImage(show);
|
setShowUploadImage(show);
|
||||||
@@ -511,18 +467,11 @@ export function ChatActions(props: {
|
|||||||
// switch to first available model
|
// switch to first available model
|
||||||
const isUnavaliableModel = !models.some((m) => m.name === currentModel);
|
const isUnavaliableModel = !models.some((m) => m.name === currentModel);
|
||||||
if (isUnavaliableModel && models.length > 0) {
|
if (isUnavaliableModel && models.length > 0) {
|
||||||
// show next model to default model if exist
|
const nextModel = models[0].name as ModelType;
|
||||||
let nextModel = models.find((model) => model.isDefault) || models[0];
|
chatStore.updateCurrentSession(
|
||||||
chatStore.updateCurrentSession((session) => {
|
(session) => (session.mask.modelConfig.model = nextModel),
|
||||||
session.mask.modelConfig.model = nextModel.name;
|
|
||||||
session.mask.modelConfig.providerName = nextModel?.provider
|
|
||||||
?.providerName as ServiceProvider;
|
|
||||||
});
|
|
||||||
showToast(
|
|
||||||
nextModel?.provider?.providerName == "ByteDance"
|
|
||||||
? nextModel.displayName
|
|
||||||
: nextModel.name,
|
|
||||||
);
|
);
|
||||||
|
showToast(nextModel);
|
||||||
}
|
}
|
||||||
}, [chatStore, currentModel, models]);
|
}, [chatStore, currentModel, models]);
|
||||||
|
|
||||||
@@ -604,149 +553,25 @@ export function ChatActions(props: {
|
|||||||
|
|
||||||
<ChatAction
|
<ChatAction
|
||||||
onClick={() => setShowModelSelector(true)}
|
onClick={() => setShowModelSelector(true)}
|
||||||
text={currentModelName}
|
text={currentModel}
|
||||||
icon={<RobotIcon />}
|
icon={<RobotIcon />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showModelSelector && (
|
{showModelSelector && (
|
||||||
<Selector
|
<Selector
|
||||||
defaultSelectedValue={`${currentModel}@${currentProviderName}`}
|
defaultSelectedValue={currentModel}
|
||||||
items={models.map((m) => ({
|
items={models.map((m) => ({
|
||||||
title: `${m.displayName}${
|
title: m.displayName,
|
||||||
m?.provider?.providerName
|
value: m.name,
|
||||||
? "(" + m?.provider?.providerName + ")"
|
|
||||||
: ""
|
|
||||||
}`,
|
|
||||||
value: `${m.name}@${m?.provider?.providerName}`,
|
|
||||||
}))}
|
}))}
|
||||||
onClose={() => setShowModelSelector(false)}
|
onClose={() => setShowModelSelector(false)}
|
||||||
onSelection={(s) => {
|
onSelection={(s) => {
|
||||||
if (s.length === 0) return;
|
if (s.length === 0) return;
|
||||||
const [model, providerName] = s[0].split("@");
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
chatStore.updateCurrentSession((session) => {
|
||||||
session.mask.modelConfig.model = model as ModelType;
|
session.mask.modelConfig.model = s[0] as ModelType;
|
||||||
session.mask.modelConfig.providerName =
|
|
||||||
providerName as ServiceProvider;
|
|
||||||
session.mask.syncGlobalConfig = false;
|
session.mask.syncGlobalConfig = false;
|
||||||
});
|
});
|
||||||
if (providerName == "ByteDance") {
|
showToast(s[0]);
|
||||||
const selectedModel = models.find(
|
|
||||||
(m) =>
|
|
||||||
m.name == model && m?.provider?.providerName == providerName,
|
|
||||||
);
|
|
||||||
showToast(selectedModel?.displayName ?? "");
|
|
||||||
} else {
|
|
||||||
showToast(model);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDalle3(currentModel) && (
|
|
||||||
<ChatAction
|
|
||||||
onClick={() => setShowSizeSelector(true)}
|
|
||||||
text={currentSize}
|
|
||||||
icon={<SizeIcon />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showSizeSelector && (
|
|
||||||
<Selector
|
|
||||||
defaultSelectedValue={currentSize}
|
|
||||||
items={dalle3Sizes.map((m) => ({
|
|
||||||
title: m,
|
|
||||||
value: m,
|
|
||||||
}))}
|
|
||||||
onClose={() => setShowSizeSelector(false)}
|
|
||||||
onSelection={(s) => {
|
|
||||||
if (s.length === 0) return;
|
|
||||||
const size = s[0];
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.mask.modelConfig.size = size;
|
|
||||||
});
|
|
||||||
showToast(size);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDalle3(currentModel) && (
|
|
||||||
<ChatAction
|
|
||||||
onClick={() => setShowQualitySelector(true)}
|
|
||||||
text={currentQuality}
|
|
||||||
icon={<QualityIcon />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showQualitySelector && (
|
|
||||||
<Selector
|
|
||||||
defaultSelectedValue={currentQuality}
|
|
||||||
items={dalle3Qualitys.map((m) => ({
|
|
||||||
title: m,
|
|
||||||
value: m,
|
|
||||||
}))}
|
|
||||||
onClose={() => setShowQualitySelector(false)}
|
|
||||||
onSelection={(q) => {
|
|
||||||
if (q.length === 0) return;
|
|
||||||
const quality = q[0];
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.mask.modelConfig.quality = quality;
|
|
||||||
});
|
|
||||||
showToast(quality);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDalle3(currentModel) && (
|
|
||||||
<ChatAction
|
|
||||||
onClick={() => setShowStyleSelector(true)}
|
|
||||||
text={currentStyle}
|
|
||||||
icon={<StyleIcon />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showStyleSelector && (
|
|
||||||
<Selector
|
|
||||||
defaultSelectedValue={currentStyle}
|
|
||||||
items={dalle3Styles.map((m) => ({
|
|
||||||
title: m,
|
|
||||||
value: m,
|
|
||||||
}))}
|
|
||||||
onClose={() => setShowStyleSelector(false)}
|
|
||||||
onSelection={(s) => {
|
|
||||||
if (s.length === 0) return;
|
|
||||||
const style = s[0];
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.mask.modelConfig.style = style;
|
|
||||||
});
|
|
||||||
showToast(style);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ChatAction
|
|
||||||
onClick={() => setShowPluginSelector(true)}
|
|
||||||
text={Locale.Plugin.Name}
|
|
||||||
icon={<PluginIcon />}
|
|
||||||
/>
|
|
||||||
{showPluginSelector && (
|
|
||||||
<Selector
|
|
||||||
multiple
|
|
||||||
defaultSelectedValue={chatStore.currentSession().mask?.plugin}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
title: Locale.Plugin.Artifacts,
|
|
||||||
value: Plugin.Artifacts,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onClose={() => setShowPluginSelector(false)}
|
|
||||||
onSelection={(s) => {
|
|
||||||
const plugin = s[0];
|
|
||||||
chatStore.updateCurrentSession((session) => {
|
|
||||||
session.mask.plugin = s;
|
|
||||||
});
|
|
||||||
if (plugin) {
|
|
||||||
showToast(plugin);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -831,7 +656,6 @@ function _Chat() {
|
|||||||
const session = chatStore.currentSession();
|
const session = chatStore.currentSession();
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const fontSize = config.fontSize;
|
const fontSize = config.fontSize;
|
||||||
const fontFamily = config.fontFamily;
|
|
||||||
|
|
||||||
const [showExport, setShowExport] = useState(false);
|
const [showExport, setShowExport] = useState(false);
|
||||||
|
|
||||||
@@ -858,7 +682,7 @@ function _Chat() {
|
|||||||
|
|
||||||
// prompt hints
|
// prompt hints
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
const [promptHints, setPromptHints] = useState<RenderPrompt[]>([]);
|
const [promptHints, setPromptHints] = useState<RenderPompt[]>([]);
|
||||||
const onSearch = useDebouncedCallback(
|
const onSearch = useDebouncedCallback(
|
||||||
(text: string) => {
|
(text: string) => {
|
||||||
const matchedPrompts = promptStore.search(text);
|
const matchedPrompts = promptStore.search(text);
|
||||||
@@ -911,7 +735,7 @@ function _Chat() {
|
|||||||
// clear search results
|
// clear search results
|
||||||
if (n === 0) {
|
if (n === 0) {
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
} else if (text.match(ChatCommandPrefix)) {
|
} else if (text.startsWith(ChatCommandPrefix)) {
|
||||||
setPromptHints(chatCommands.search(text));
|
setPromptHints(chatCommands.search(text));
|
||||||
} else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
} else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
||||||
// check if need to trigger auto completion
|
// check if need to trigger auto completion
|
||||||
@@ -943,7 +767,7 @@ function _Chat() {
|
|||||||
setAutoScroll(true);
|
setAutoScroll(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPromptSelect = (prompt: RenderPrompt) => {
|
const onPromptSelect = (prompt: RenderPompt) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
|
|
||||||
@@ -1251,7 +1075,6 @@ function _Chat() {
|
|||||||
if (payload.url) {
|
if (payload.url) {
|
||||||
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
accessStore.update((access) => (access.openaiUrl = payload.url!));
|
||||||
}
|
}
|
||||||
accessStore.update((access) => (access.useCustomConfig = true));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1279,13 +1102,11 @@ function _Chat() {
|
|||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePaste = useCallback(
|
const handlePaste = useCallback(
|
||||||
async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||||
if (!isVisionModel(currentModel)) {
|
if(!isVisionModel(currentModel)){return;}
|
||||||
return;
|
|
||||||
}
|
|
||||||
const items = (event.clipboardData || window.clipboardData).items;
|
const items = (event.clipboardData || window.clipboardData).items;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.kind === "file" && item.type.startsWith("image/")) {
|
if (item.kind === "file" && item.type.startsWith("image/")) {
|
||||||
@@ -1298,7 +1119,7 @@ function _Chat() {
|
|||||||
...(await new Promise<string[]>((res, rej) => {
|
...(await new Promise<string[]>((res, rej) => {
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
const imagesData: string[] = [];
|
const imagesData: string[] = [];
|
||||||
uploadImageRemote(file)
|
compressImage(file, 256 * 1024)
|
||||||
.then((dataUrl) => {
|
.then((dataUrl) => {
|
||||||
imagesData.push(dataUrl);
|
imagesData.push(dataUrl);
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
@@ -1340,7 +1161,7 @@ function _Chat() {
|
|||||||
const imagesData: string[] = [];
|
const imagesData: string[] = [];
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = event.target.files[i];
|
const file = event.target.files[i];
|
||||||
uploadImageRemote(file)
|
compressImage(file, 256 * 1024)
|
||||||
.then((dataUrl) => {
|
.then((dataUrl) => {
|
||||||
imagesData.push(dataUrl);
|
imagesData.push(dataUrl);
|
||||||
if (
|
if (
|
||||||
@@ -1401,8 +1222,6 @@ function _Chat() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<RenameIcon />}
|
icon={<RenameIcon />}
|
||||||
bordered
|
bordered
|
||||||
title={Locale.Chat.EditMessage.Title}
|
|
||||||
aria={Locale.Chat.EditMessage.Title}
|
|
||||||
onClick={() => setIsEditingMessage(true)}
|
onClick={() => setIsEditingMessage(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1422,8 +1241,6 @@ function _Chat() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
||||||
bordered
|
bordered
|
||||||
title={Locale.Chat.Actions.FullScreen}
|
|
||||||
aria={Locale.Chat.Actions.FullScreen}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
config.update(
|
config.update(
|
||||||
(config) => (config.tightBorder = !config.tightBorder),
|
(config) => (config.tightBorder = !config.tightBorder),
|
||||||
@@ -1475,7 +1292,6 @@ function _Chat() {
|
|||||||
<div className={styles["chat-message-edit"]}>
|
<div className={styles["chat-message-edit"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
aria={Locale.Chat.Actions.Edit}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const newMessage = await showPrompt(
|
const newMessage = await showPrompt(
|
||||||
Locale.Chat.Actions.Edit,
|
Locale.Chat.Actions.Edit,
|
||||||
@@ -1524,11 +1340,6 @@ function _Chat() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isUser && (
|
|
||||||
<div className={styles["chat-model-name"]}>
|
|
||||||
{message.model}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<div className={styles["chat-message-actions"]}>
|
<div className={styles["chat-message-actions"]}>
|
||||||
@@ -1580,7 +1391,6 @@ function _Chat() {
|
|||||||
)}
|
)}
|
||||||
<div className={styles["chat-message-item"]}>
|
<div className={styles["chat-message-item"]}>
|
||||||
<Markdown
|
<Markdown
|
||||||
key={message.streaming ? "loading" : "done"}
|
|
||||||
content={getMessageTextContent(message)}
|
content={getMessageTextContent(message)}
|
||||||
loading={
|
loading={
|
||||||
(message.preview || message.streaming) &&
|
(message.preview || message.streaming) &&
|
||||||
@@ -1593,7 +1403,6 @@ function _Chat() {
|
|||||||
setUserInput(getMessageTextContent(message));
|
setUserInput(getMessageTextContent(message));
|
||||||
}}
|
}}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
fontFamily={fontFamily}
|
|
||||||
parentRef={scrollRef}
|
parentRef={scrollRef}
|
||||||
defaultShow={i >= messages.length - 6}
|
defaultShow={i >= messages.length - 6}
|
||||||
/>
|
/>
|
||||||
@@ -1688,7 +1497,6 @@ function _Chat() {
|
|||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
style={{
|
style={{
|
||||||
fontSize: config.fontSize,
|
fontSize: config.fontSize,
|
||||||
fontFamily: config.fontFamily,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{attachImages.length != 0 && (
|
{attachImages.length != 0 && (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ import { toBlob, toPng } from "html-to-image";
|
|||||||
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
||||||
|
|
||||||
import { prettyObject } from "../utils/format";
|
import { prettyObject } from "../utils/format";
|
||||||
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
|
import { EXPORT_MESSAGE_CLASS_NAME, ModelProvider } from "../constant";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { type ClientApi, getClientApi } from "../client/api";
|
import { ClientApi } from "../client/api";
|
||||||
import { getMessageTextContent } from "../utils";
|
import { getMessageTextContent } from "../utils";
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
@@ -312,7 +312,14 @@ export function PreviewActions(props: {
|
|||||||
const onRenderMsgs = (msgs: ChatMessage[]) => {
|
const onRenderMsgs = (msgs: ChatMessage[]) => {
|
||||||
setShouldExport(false);
|
setShouldExport(false);
|
||||||
|
|
||||||
const api: ClientApi = getClientApi(config.modelConfig.providerName);
|
var api: ClientApi;
|
||||||
|
if (config.modelConfig.model.startsWith("gemini")) {
|
||||||
|
api = new ClientApi(ModelProvider.GeminiPro);
|
||||||
|
} else if (config.modelConfig.model.startsWith("claude")) {
|
||||||
|
api = new ClientApi(ModelProvider.Claude);
|
||||||
|
} else {
|
||||||
|
api = new ClientApi(ModelProvider.GPT);
|
||||||
|
}
|
||||||
|
|
||||||
api
|
api
|
||||||
.share(msgs)
|
.share(msgs)
|
||||||
@@ -541,7 +548,7 @@ export function ImagePreviewer(props: {
|
|||||||
<div>
|
<div>
|
||||||
<div className={styles["main-title"]}>NextChat</div>
|
<div className={styles["main-title"]}>NextChat</div>
|
||||||
<div className={styles["sub-title"]}>
|
<div className={styles["sub-title"]}>
|
||||||
github.com/ChatGPTNextWeb/ChatGPT-Next-Web
|
github.com/Yidadaa/ChatGPT-Next-Web
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["icons"]}>
|
<div className={styles["icons"]}>
|
||||||
<ExportAvatar avatar={config.avatar} />
|
<ExportAvatar avatar={config.avatar} />
|
||||||
@@ -583,7 +590,6 @@ export function ImagePreviewer(props: {
|
|||||||
<Markdown
|
<Markdown
|
||||||
content={getMessageTextContent(m)}
|
content={getMessageTextContent(m)}
|
||||||
fontSize={config.fontSize}
|
fontSize={config.fontSize}
|
||||||
fontFamily={config.fontFamily}
|
|
||||||
defaultShow
|
defaultShow
|
||||||
/>
|
/>
|
||||||
{getMessageImages(m).length == 1 && (
|
{getMessageImages(m).length == 1 && (
|
||||||
|
|||||||
@@ -137,18 +137,12 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo {
|
.sidebar-logo {
|
||||||
display: inline-flex;
|
position: absolute;
|
||||||
}
|
right: 0;
|
||||||
|
bottom: 18px;
|
||||||
.sidebar-title-container {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import LoadingIcon from "../icons/three-dots.svg";
|
|||||||
import { getCSSVar, useMobileScreen } from "../utils";
|
import { getCSSVar, useMobileScreen } from "../utils";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Path, SlotID } from "../constant";
|
import { ModelProvider, Path, SlotID } from "../constant";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
|
|
||||||
import { getISOLang, getLang } from "../locales";
|
import { getISOLang, getLang } from "../locales";
|
||||||
@@ -27,7 +27,7 @@ import { SideBar } from "./sidebar";
|
|||||||
import { useAppConfig } from "../store/config";
|
import { useAppConfig } from "../store/config";
|
||||||
import { AuthPage } from "./auth";
|
import { AuthPage } from "./auth";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { type ClientApi, getClientApi } from "../client/api";
|
import { ClientApi } from "../client/api";
|
||||||
import { useAccessStore } from "../store";
|
import { useAccessStore } from "../store";
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
@@ -39,10 +39,6 @@ export function Loading(props: { noLogo?: boolean }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Artifacts = dynamic(async () => (await import("./artifacts")).Artifacts, {
|
|
||||||
loading: () => <Loading noLogo />,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Settings = dynamic(async () => (await import("./settings")).Settings, {
|
const Settings = dynamic(async () => (await import("./settings")).Settings, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
@@ -59,10 +55,6 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
|
|||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
|
||||||
loading: () => <Loading noLogo />,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useSwitchTheme() {
|
export function useSwitchTheme() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
|
||||||
@@ -130,23 +122,11 @@ const loadAsyncGoogleFont = () => {
|
|||||||
document.head.appendChild(linkEl);
|
document.head.appendChild(linkEl);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WindowContent(props: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className={styles["window-content"]} id={SlotID.AppBody}>
|
|
||||||
{props?.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isArtifact = location.pathname.includes(Path.Artifacts);
|
|
||||||
const isHome = location.pathname === Path.Home;
|
const isHome = location.pathname === Path.Home;
|
||||||
const isAuth = location.pathname === Path.Auth;
|
const isAuth = location.pathname === Path.Auth;
|
||||||
const isSd = location.pathname === Path.Sd;
|
|
||||||
const isSdNew = location.pathname === Path.SdNew;
|
|
||||||
|
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
const shouldTightBorder =
|
const shouldTightBorder =
|
||||||
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
||||||
@@ -155,40 +135,34 @@ function Screen() {
|
|||||||
loadAsyncGoogleFont();
|
loadAsyncGoogleFont();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isArtifact) {
|
|
||||||
return (
|
|
||||||
<Routes>
|
|
||||||
<Route path="/artifacts/:id" element={<Artifacts />} />
|
|
||||||
</Routes>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const renderContent = () => {
|
|
||||||
if (isAuth) return <AuthPage />;
|
|
||||||
if (isSd) return <Sd />;
|
|
||||||
if (isSdNew) return <Sd />;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
|
||||||
<WindowContent>
|
|
||||||
<Routes>
|
|
||||||
<Route path={Path.Home} element={<Chat />} />
|
|
||||||
<Route path={Path.NewChat} element={<NewChat />} />
|
|
||||||
<Route path={Path.Masks} element={<MaskPage />} />
|
|
||||||
<Route path={Path.Chat} element={<Chat />} />
|
|
||||||
<Route path={Path.Settings} element={<Settings />} />
|
|
||||||
</Routes>
|
|
||||||
</WindowContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.container} ${
|
className={
|
||||||
shouldTightBorder ? styles["tight-container"] : styles.container
|
styles.container +
|
||||||
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`}
|
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
|
||||||
|
getLang() === "ar" ? styles["rtl-screen"] : ""
|
||||||
|
}`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{renderContent()}
|
{isAuth ? (
|
||||||
|
<>
|
||||||
|
<AuthPage />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
||||||
|
|
||||||
|
<div className={styles["window-content"]} id={SlotID.AppBody}>
|
||||||
|
<Routes>
|
||||||
|
<Route path={Path.Home} element={<Chat />} />
|
||||||
|
<Route path={Path.NewChat} element={<NewChat />} />
|
||||||
|
<Route path={Path.Masks} element={<MaskPage />} />
|
||||||
|
<Route path={Path.Chat} element={<Chat />} />
|
||||||
|
<Route path={Path.Settings} element={<Settings />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -196,8 +170,14 @@ function Screen() {
|
|||||||
export function useLoadData() {
|
export function useLoadData() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
|
||||||
const api: ClientApi = getClientApi(config.modelConfig.providerName);
|
var api: ClientApi;
|
||||||
|
if (config.modelConfig.model.startsWith("gemini")) {
|
||||||
|
api = new ClientApi(ModelProvider.GeminiPro);
|
||||||
|
} else if (config.modelConfig.model.startsWith("claude")) {
|
||||||
|
api = new ClientApi(ModelProvider.Claude);
|
||||||
|
} else {
|
||||||
|
api = new ClientApi(ModelProvider.GPT);
|
||||||
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const models = await api.llm.models();
|
const models = await api.llm.models();
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ interface InputRangeProps {
|
|||||||
min: string;
|
min: string;
|
||||||
max: string;
|
max: string;
|
||||||
step: string;
|
step: string;
|
||||||
aria: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputRange({
|
export function InputRange({
|
||||||
@@ -20,13 +19,11 @@ export function InputRange({
|
|||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
step,
|
step,
|
||||||
aria,
|
|
||||||
}: InputRangeProps) {
|
}: InputRangeProps) {
|
||||||
return (
|
return (
|
||||||
<div className={styles["input-range"] + ` ${className ?? ""}`}>
|
<div className={styles["input-range"] + ` ${className ?? ""}`}>
|
||||||
{title || value}
|
{title || value}
|
||||||
<input
|
<input
|
||||||
aria-label={aria}
|
|
||||||
type="range"
|
type="range"
|
||||||
title={title}
|
title={title}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -6,16 +6,14 @@ import RehypeKatex from "rehype-katex";
|
|||||||
import RemarkGfm from "remark-gfm";
|
import RemarkGfm from "remark-gfm";
|
||||||
import RehypeHighlight from "rehype-highlight";
|
import RehypeHighlight from "rehype-highlight";
|
||||||
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
|
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
|
||||||
import { copyToClipboard, useWindowSize } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
import mermaid from "mermaid";
|
import mermaid from "mermaid";
|
||||||
|
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { showImageModal, FullScreen } from "./ui-lib";
|
import { showImageModal } from "./ui-lib";
|
||||||
import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
|
|
||||||
import { Plugin } from "../constant";
|
|
||||||
import { useChatStore } from "../store";
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
@@ -66,64 +64,25 @@ export function PreCode(props: { children: any }) {
|
|||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
const refText = ref.current?.innerText;
|
const refText = ref.current?.innerText;
|
||||||
const [mermaidCode, setMermaidCode] = useState("");
|
const [mermaidCode, setMermaidCode] = useState("");
|
||||||
const [htmlCode, setHtmlCode] = useState("");
|
|
||||||
const { height } = useWindowSize();
|
|
||||||
const chatStore = useChatStore();
|
|
||||||
const session = chatStore.currentSession();
|
|
||||||
const plugins = session.mask?.plugin;
|
|
||||||
|
|
||||||
const renderArtifacts = useDebouncedCallback(() => {
|
const renderMermaid = useDebouncedCallback(() => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
const mermaidDom = ref.current.querySelector("code.language-mermaid");
|
const mermaidDom = ref.current.querySelector("code.language-mermaid");
|
||||||
if (mermaidDom) {
|
if (mermaidDom) {
|
||||||
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
||||||
}
|
}
|
||||||
const htmlDom = ref.current.querySelector("code.language-html");
|
|
||||||
if (htmlDom) {
|
|
||||||
setHtmlCode((htmlDom as HTMLElement).innerText);
|
|
||||||
} else if (refText?.startsWith("<!DOCTYPE")) {
|
|
||||||
setHtmlCode(refText);
|
|
||||||
}
|
|
||||||
}, 600);
|
}, 600);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(renderArtifacts, 1);
|
setTimeout(renderMermaid, 1);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [refText]);
|
}, [refText]);
|
||||||
|
|
||||||
const enableArtifacts = useMemo(
|
|
||||||
() => plugins?.includes(Plugin.Artifacts),
|
|
||||||
[plugins],
|
|
||||||
);
|
|
||||||
|
|
||||||
//Wrap the paragraph for plain-text
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) {
|
|
||||||
const codeElements = ref.current.querySelectorAll(
|
|
||||||
"code",
|
|
||||||
) as NodeListOf<HTMLElement>;
|
|
||||||
const wrapLanguages = [
|
|
||||||
"",
|
|
||||||
"md",
|
|
||||||
"markdown",
|
|
||||||
"text",
|
|
||||||
"txt",
|
|
||||||
"plaintext",
|
|
||||||
"tex",
|
|
||||||
"latex",
|
|
||||||
];
|
|
||||||
codeElements.forEach((codeElement) => {
|
|
||||||
let languageClass = codeElement.className.match(/language-(\w+)/);
|
|
||||||
let name = languageClass ? languageClass[1] : "";
|
|
||||||
if (wrapLanguages.includes(name)) {
|
|
||||||
codeElement.style.whiteSpace = "pre-wrap";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{mermaidCode.length > 0 && (
|
||||||
|
<Mermaid code={mermaidCode} key={mermaidCode} />
|
||||||
|
)}
|
||||||
<pre ref={ref}>
|
<pre ref={ref}>
|
||||||
<span
|
<span
|
||||||
className="copy-code-button"
|
className="copy-code-button"
|
||||||
@@ -136,62 +95,6 @@ export function PreCode(props: { children: any }) {
|
|||||||
></span>
|
></span>
|
||||||
{props.children}
|
{props.children}
|
||||||
</pre>
|
</pre>
|
||||||
{mermaidCode.length > 0 && (
|
|
||||||
<Mermaid code={mermaidCode} key={mermaidCode} />
|
|
||||||
)}
|
|
||||||
{htmlCode.length > 0 && enableArtifacts && (
|
|
||||||
<FullScreen className="no-dark html" right={70}>
|
|
||||||
<ArtifactsShareButton
|
|
||||||
style={{ position: "absolute", right: 20, top: 10 }}
|
|
||||||
getCode={() => htmlCode}
|
|
||||||
/>
|
|
||||||
<HTMLPreview
|
|
||||||
code={htmlCode}
|
|
||||||
autoHeight={!document.fullscreenElement}
|
|
||||||
height={!document.fullscreenElement ? 600 : height}
|
|
||||||
/>
|
|
||||||
</FullScreen>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CustomCode(props: { children: any }) {
|
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
|
||||||
const [showToggle, setShowToggle] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) {
|
|
||||||
const codeHeight = ref.current.scrollHeight;
|
|
||||||
setShowToggle(codeHeight > 400);
|
|
||||||
ref.current.scrollTop = ref.current.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [props.children]);
|
|
||||||
|
|
||||||
const toggleCollapsed = () => {
|
|
||||||
setCollapsed((collapsed) => !collapsed);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<code
|
|
||||||
ref={ref}
|
|
||||||
style={{
|
|
||||||
maxHeight: collapsed ? "400px" : "none",
|
|
||||||
overflowY: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
{showToggle && collapsed && (
|
|
||||||
<div
|
|
||||||
className={`show-hide-button ${
|
|
||||||
collapsed ? "collapsed" : "expanded"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button onClick={toggleCollapsed}>查看全部</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</code>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -232,9 +135,10 @@ function escapeBrackets(text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _MarkDownContent(props: { content: string }) {
|
function _MarkDownContent(props: { content: string }) {
|
||||||
const escapedContent = useMemo(() => {
|
const escapedContent = useMemo(
|
||||||
return escapeBrackets(escapeDollarNumber(props.content));
|
() => escapeBrackets(escapeDollarNumber(props.content)),
|
||||||
}, [props.content]);
|
[props.content],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
@@ -251,7 +155,6 @@ function _MarkDownContent(props: { content: string }) {
|
|||||||
]}
|
]}
|
||||||
components={{
|
components={{
|
||||||
pre: PreCode,
|
pre: PreCode,
|
||||||
code: CustomCode,
|
|
||||||
p: (pProps) => <p {...pProps} dir="auto" />,
|
p: (pProps) => <p {...pProps} dir="auto" />,
|
||||||
a: (aProps) => {
|
a: (aProps) => {
|
||||||
const href = aProps.href || "";
|
const href = aProps.href || "";
|
||||||
@@ -273,7 +176,6 @@ export function Markdown(
|
|||||||
content: string;
|
content: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
fontFamily?: string;
|
|
||||||
parentRef?: RefObject<HTMLDivElement>;
|
parentRef?: RefObject<HTMLDivElement>;
|
||||||
defaultShow?: boolean;
|
defaultShow?: boolean;
|
||||||
} & React.DOMAttributes<HTMLDivElement>,
|
} & React.DOMAttributes<HTMLDivElement>,
|
||||||
@@ -285,7 +187,6 @@ export function Markdown(
|
|||||||
className="markdown-body"
|
className="markdown-body"
|
||||||
style={{
|
style={{
|
||||||
fontSize: `${props.fontSize ?? 14}px`,
|
fontSize: `${props.fontSize ?? 14}px`,
|
||||||
fontFamily: props.fontFamily || "inherit",
|
|
||||||
}}
|
}}
|
||||||
ref={mdRef}
|
ref={mdRef}
|
||||||
onContextMenu={props.onContextMenu}
|
onContextMenu={props.onContextMenu}
|
||||||
|
|||||||
@@ -127,8 +127,6 @@ export function MaskConfig(props: {
|
|||||||
onClose={() => setShowPicker(false)}
|
onClose={() => setShowPicker(false)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
|
||||||
aria-label={Locale.Mask.Config.Avatar}
|
|
||||||
onClick={() => setShowPicker(true)}
|
onClick={() => setShowPicker(true)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
@@ -141,7 +139,6 @@ export function MaskConfig(props: {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem title={Locale.Mask.Config.Name}>
|
<ListItem title={Locale.Mask.Config.Name}>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Mask.Config.Name}
|
|
||||||
type="text"
|
type="text"
|
||||||
value={props.mask.name}
|
value={props.mask.name}
|
||||||
onInput={(e) =>
|
onInput={(e) =>
|
||||||
@@ -156,7 +153,6 @@ export function MaskConfig(props: {
|
|||||||
subTitle={Locale.Mask.Config.HideContext.SubTitle}
|
subTitle={Locale.Mask.Config.HideContext.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Mask.Config.HideContext.Title}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.mask.hideContext}
|
checked={props.mask.hideContext}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -173,7 +169,6 @@ export function MaskConfig(props: {
|
|||||||
subTitle={Locale.Mask.Config.Share.SubTitle}
|
subTitle={Locale.Mask.Config.Share.SubTitle}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria={Locale.Mask.Config.Share.Title}
|
|
||||||
icon={<CopyIcon />}
|
icon={<CopyIcon />}
|
||||||
text={Locale.Mask.Config.Share.Action}
|
text={Locale.Mask.Config.Share.Action}
|
||||||
onClick={copyMaskLink}
|
onClick={copyMaskLink}
|
||||||
@@ -187,7 +182,6 @@ export function MaskConfig(props: {
|
|||||||
subTitle={Locale.Mask.Config.Sync.SubTitle}
|
subTitle={Locale.Mask.Config.Sync.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Mask.Config.Sync.Title}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.mask.syncGlobalConfig}
|
checked={props.mask.syncGlobalConfig}
|
||||||
onChange={async (e) => {
|
onChange={async (e) => {
|
||||||
@@ -411,7 +405,7 @@ export function MaskPage() {
|
|||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
const [filterLang, setFilterLang] = useState<Lang | undefined>(
|
const [filterLang, setFilterLang] = useState<Lang | undefined>(
|
||||||
() => localStorage.getItem("Mask-language") as Lang | undefined,
|
localStorage.getItem("Mask-language") as Lang | undefined,
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filterLang) {
|
if (filterLang) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ServiceProvider } from "@/app/constant";
|
|
||||||
import { ModalConfigValidator, ModelConfig } from "../store";
|
import { ModalConfigValidator, ModelConfig } from "../store";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
@@ -11,26 +10,25 @@ export function ModelConfigList(props: {
|
|||||||
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
||||||
}) {
|
}) {
|
||||||
const allModels = useAllModels();
|
const allModels = useAllModels();
|
||||||
const value = `${props.modelConfig.model}@${props.modelConfig?.providerName}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem title={Locale.Settings.Model}>
|
<ListItem title={Locale.Settings.Model}>
|
||||||
<Select
|
<Select
|
||||||
aria-label={Locale.Settings.Model}
|
value={props.modelConfig.model}
|
||||||
value={value}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const [model, providerName] = e.currentTarget.value.split("@");
|
props.updateConfig(
|
||||||
props.updateConfig((config) => {
|
(config) =>
|
||||||
config.model = ModalConfigValidator.model(model);
|
(config.model = ModalConfigValidator.model(
|
||||||
config.providerName = providerName as ServiceProvider;
|
e.currentTarget.value,
|
||||||
});
|
)),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{allModels
|
{allModels
|
||||||
.filter((v) => v.available)
|
.filter((v) => v.available)
|
||||||
.map((v, i) => (
|
.map((v, i) => (
|
||||||
<option value={`${v.name}@${v.provider?.providerName}`} key={i}>
|
<option value={v.name} key={i}>
|
||||||
{v.displayName}({v.provider?.providerName})
|
{v.displayName}({v.provider?.providerName})
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -41,7 +39,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.Temperature.SubTitle}
|
subTitle={Locale.Settings.Temperature.SubTitle}
|
||||||
>
|
>
|
||||||
<InputRange
|
<InputRange
|
||||||
aria={Locale.Settings.Temperature.Title}
|
|
||||||
value={props.modelConfig.temperature?.toFixed(1)}
|
value={props.modelConfig.temperature?.toFixed(1)}
|
||||||
min="0"
|
min="0"
|
||||||
max="1" // lets limit it to 0-1
|
max="1" // lets limit it to 0-1
|
||||||
@@ -61,7 +58,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.TopP.SubTitle}
|
subTitle={Locale.Settings.TopP.SubTitle}
|
||||||
>
|
>
|
||||||
<InputRange
|
<InputRange
|
||||||
aria={Locale.Settings.TopP.Title}
|
|
||||||
value={(props.modelConfig.top_p ?? 1).toFixed(1)}
|
value={(props.modelConfig.top_p ?? 1).toFixed(1)}
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
@@ -81,7 +77,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.MaxTokens.SubTitle}
|
subTitle={Locale.Settings.MaxTokens.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Settings.MaxTokens.Title}
|
|
||||||
type="number"
|
type="number"
|
||||||
min={1024}
|
min={1024}
|
||||||
max={512000}
|
max={512000}
|
||||||
@@ -97,14 +92,13 @@ export function ModelConfigList(props: {
|
|||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{props.modelConfig?.providerName == ServiceProvider.Google ? null : (
|
{props.modelConfig.model.startsWith("gemini") ? null : (
|
||||||
<>
|
<>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.PresencePenalty.Title}
|
title={Locale.Settings.PresencePenalty.Title}
|
||||||
subTitle={Locale.Settings.PresencePenalty.SubTitle}
|
subTitle={Locale.Settings.PresencePenalty.SubTitle}
|
||||||
>
|
>
|
||||||
<InputRange
|
<InputRange
|
||||||
aria={Locale.Settings.PresencePenalty.Title}
|
|
||||||
value={props.modelConfig.presence_penalty?.toFixed(1)}
|
value={props.modelConfig.presence_penalty?.toFixed(1)}
|
||||||
min="-2"
|
min="-2"
|
||||||
max="2"
|
max="2"
|
||||||
@@ -126,7 +120,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.FrequencyPenalty.SubTitle}
|
subTitle={Locale.Settings.FrequencyPenalty.SubTitle}
|
||||||
>
|
>
|
||||||
<InputRange
|
<InputRange
|
||||||
aria={Locale.Settings.FrequencyPenalty.Title}
|
|
||||||
value={props.modelConfig.frequency_penalty?.toFixed(1)}
|
value={props.modelConfig.frequency_penalty?.toFixed(1)}
|
||||||
min="-2"
|
min="-2"
|
||||||
max="2"
|
max="2"
|
||||||
@@ -148,7 +141,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.InjectSystemPrompts.SubTitle}
|
subTitle={Locale.Settings.InjectSystemPrompts.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Settings.InjectSystemPrompts.Title}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.modelConfig.enableInjectSystemPrompts}
|
checked={props.modelConfig.enableInjectSystemPrompts}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -166,7 +158,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.InputTemplate.SubTitle}
|
subTitle={Locale.Settings.InputTemplate.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Settings.InputTemplate.Title}
|
|
||||||
type="text"
|
type="text"
|
||||||
value={props.modelConfig.template}
|
value={props.modelConfig.template}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -183,7 +174,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.HistoryCount.SubTitle}
|
subTitle={Locale.Settings.HistoryCount.SubTitle}
|
||||||
>
|
>
|
||||||
<InputRange
|
<InputRange
|
||||||
aria={Locale.Settings.HistoryCount.Title}
|
|
||||||
title={props.modelConfig.historyMessageCount.toString()}
|
title={props.modelConfig.historyMessageCount.toString()}
|
||||||
value={props.modelConfig.historyMessageCount}
|
value={props.modelConfig.historyMessageCount}
|
||||||
min="0"
|
min="0"
|
||||||
@@ -202,7 +192,6 @@ export function ModelConfigList(props: {
|
|||||||
subTitle={Locale.Settings.CompressThreshold.SubTitle}
|
subTitle={Locale.Settings.CompressThreshold.SubTitle}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Settings.CompressThreshold.Title}
|
|
||||||
type="number"
|
type="number"
|
||||||
min={500}
|
min={500}
|
||||||
max={4000}
|
max={4000}
|
||||||
@@ -218,7 +207,6 @@ export function ModelConfigList(props: {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem title={Locale.Memory.Title} subTitle={Locale.Memory.Send}>
|
<ListItem title={Locale.Memory.Title} subTitle={Locale.Memory.Send}>
|
||||||
<input
|
<input
|
||||||
aria-label={Locale.Memory.Title}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.modelConfig.sendMemory}
|
checked={props.modelConfig.sendMemory}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./sd";
|
|
||||||
export * from "./sd-panel";
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
.ctrl-param-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-height: 40px;
|
|
||||||
padding: 10px 0;
|
|
||||||
animation: slide-in ease 0.6s;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.ctrl-param-item-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.ctrl-param-item-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bolder;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ctrl-param-item-sub-title {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
appearance: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: var(--border-in-light);
|
|
||||||
min-height: 36px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: var(--white);
|
|
||||||
color: var(--black);
|
|
||||||
padding: 0 10px;
|
|
||||||
max-width: 50%;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ai-models {
|
|
||||||
button {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
import styles from "./sd-panel.module.scss";
|
|
||||||
import React from "react";
|
|
||||||
import { Select } from "@/app/components/ui-lib";
|
|
||||||
import { IconButton } from "@/app/components/button";
|
|
||||||
import Locale from "@/app/locales";
|
|
||||||
import { useSdStore } from "@/app/store/sd";
|
|
||||||
|
|
||||||
export const params = [
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.Prompt,
|
|
||||||
value: "prompt",
|
|
||||||
type: "textarea",
|
|
||||||
placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.Prompt),
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.ModelVersion,
|
|
||||||
value: "model",
|
|
||||||
type: "select",
|
|
||||||
default: "sd3-medium",
|
|
||||||
support: ["sd3"],
|
|
||||||
options: [
|
|
||||||
{ name: "SD3 Medium", value: "sd3-medium" },
|
|
||||||
{ name: "SD3 Large", value: "sd3-large" },
|
|
||||||
{ name: "SD3 Large Turbo", value: "sd3-large-turbo" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.NegativePrompt,
|
|
||||||
value: "negative_prompt",
|
|
||||||
type: "textarea",
|
|
||||||
placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.NegativePrompt),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.AspectRatio,
|
|
||||||
value: "aspect_ratio",
|
|
||||||
type: "select",
|
|
||||||
default: "1:1",
|
|
||||||
options: [
|
|
||||||
{ name: "1:1", value: "1:1" },
|
|
||||||
{ name: "16:9", value: "16:9" },
|
|
||||||
{ name: "21:9", value: "21:9" },
|
|
||||||
{ name: "2:3", value: "2:3" },
|
|
||||||
{ name: "3:2", value: "3:2" },
|
|
||||||
{ name: "4:5", value: "4:5" },
|
|
||||||
{ name: "5:4", value: "5:4" },
|
|
||||||
{ name: "9:16", value: "9:16" },
|
|
||||||
{ name: "9:21", value: "9:21" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.ImageStyle,
|
|
||||||
value: "style",
|
|
||||||
type: "select",
|
|
||||||
default: "3d-model",
|
|
||||||
support: ["core"],
|
|
||||||
options: [
|
|
||||||
{ name: Locale.SdPanel.Styles.D3Model, value: "3d-model" },
|
|
||||||
{ name: Locale.SdPanel.Styles.AnalogFilm, value: "analog-film" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Anime, value: "anime" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Cinematic, value: "cinematic" },
|
|
||||||
{ name: Locale.SdPanel.Styles.ComicBook, value: "comic-book" },
|
|
||||||
{ name: Locale.SdPanel.Styles.DigitalArt, value: "digital-art" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Enhance, value: "enhance" },
|
|
||||||
{ name: Locale.SdPanel.Styles.FantasyArt, value: "fantasy-art" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Isometric, value: "isometric" },
|
|
||||||
{ name: Locale.SdPanel.Styles.LineArt, value: "line-art" },
|
|
||||||
{ name: Locale.SdPanel.Styles.LowPoly, value: "low-poly" },
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.Styles.ModelingCompound,
|
|
||||||
value: "modeling-compound",
|
|
||||||
},
|
|
||||||
{ name: Locale.SdPanel.Styles.NeonPunk, value: "neon-punk" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Origami, value: "origami" },
|
|
||||||
{ name: Locale.SdPanel.Styles.Photographic, value: "photographic" },
|
|
||||||
{ name: Locale.SdPanel.Styles.PixelArt, value: "pixel-art" },
|
|
||||||
{ name: Locale.SdPanel.Styles.TileTexture, value: "tile-texture" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Seed",
|
|
||||||
value: "seed",
|
|
||||||
type: "number",
|
|
||||||
default: 0,
|
|
||||||
min: 0,
|
|
||||||
max: 4294967294,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: Locale.SdPanel.OutFormat,
|
|
||||||
value: "output_format",
|
|
||||||
type: "select",
|
|
||||||
default: "png",
|
|
||||||
options: [
|
|
||||||
{ name: "PNG", value: "png" },
|
|
||||||
{ name: "JPEG", value: "jpeg" },
|
|
||||||
{ name: "WebP", value: "webp" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const sdCommonParams = (model: string, data: any) => {
|
|
||||||
return params.filter((item) => {
|
|
||||||
return !(item.support && !item.support.includes(model));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const models = [
|
|
||||||
{
|
|
||||||
name: "Stable Image Ultra",
|
|
||||||
value: "ultra",
|
|
||||||
params: (data: any) => sdCommonParams("ultra", data),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Stable Image Core",
|
|
||||||
value: "core",
|
|
||||||
params: (data: any) => sdCommonParams("core", data),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Stable Diffusion 3",
|
|
||||||
value: "sd3",
|
|
||||||
params: (data: any) => {
|
|
||||||
return sdCommonParams("sd3", data).filter((item) => {
|
|
||||||
return !(
|
|
||||||
data.model === "sd3-large-turbo" && item.value == "negative_prompt"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function ControlParamItem(props: {
|
|
||||||
title: string;
|
|
||||||
subTitle?: string;
|
|
||||||
required?: boolean;
|
|
||||||
children?: JSX.Element | JSX.Element[];
|
|
||||||
className?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className={styles["ctrl-param-item"] + ` ${props.className || ""}`}>
|
|
||||||
<div className={styles["ctrl-param-item-header"]}>
|
|
||||||
<div className={styles["ctrl-param-item-title"]}>
|
|
||||||
<div>
|
|
||||||
{props.title}
|
|
||||||
{props.required && <span style={{ color: "red" }}>*</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{props.children}
|
|
||||||
{props.subTitle && (
|
|
||||||
<div className={styles["ctrl-param-item-sub-title"]}>
|
|
||||||
{props.subTitle}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ControlParam(props: {
|
|
||||||
columns: any[];
|
|
||||||
data: any;
|
|
||||||
onChange: (field: string, val: any) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{props.columns?.map((item) => {
|
|
||||||
let element: null | JSX.Element;
|
|
||||||
switch (item.type) {
|
|
||||||
case "textarea":
|
|
||||||
element = (
|
|
||||||
<ControlParamItem
|
|
||||||
title={item.name}
|
|
||||||
subTitle={item.sub}
|
|
||||||
required={item.required}
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
rows={item.rows || 3}
|
|
||||||
style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
|
|
||||||
placeholder={item.placeholder}
|
|
||||||
onChange={(e) => {
|
|
||||||
props.onChange(item.value, e.currentTarget.value);
|
|
||||||
}}
|
|
||||||
value={props.data[item.value]}
|
|
||||||
></textarea>
|
|
||||||
</ControlParamItem>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "select":
|
|
||||||
element = (
|
|
||||||
<ControlParamItem
|
|
||||||
title={item.name}
|
|
||||||
subTitle={item.sub}
|
|
||||||
required={item.required}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
aria-label={item.name}
|
|
||||||
value={props.data[item.value]}
|
|
||||||
onChange={(e) => {
|
|
||||||
props.onChange(item.value, e.currentTarget.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.options.map((opt: any) => {
|
|
||||||
return (
|
|
||||||
<option value={opt.value} key={opt.value}>
|
|
||||||
{opt.name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</ControlParamItem>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "number":
|
|
||||||
element = (
|
|
||||||
<ControlParamItem
|
|
||||||
title={item.name}
|
|
||||||
subTitle={item.sub}
|
|
||||||
required={item.required}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label={item.name}
|
|
||||||
type="number"
|
|
||||||
min={item.min}
|
|
||||||
max={item.max}
|
|
||||||
value={props.data[item.value] || 0}
|
|
||||||
onChange={(e) => {
|
|
||||||
props.onChange(item.value, parseInt(e.currentTarget.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ControlParamItem>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
element = (
|
|
||||||
<ControlParamItem
|
|
||||||
title={item.name}
|
|
||||||
subTitle={item.sub}
|
|
||||||
required={item.required}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label={item.name}
|
|
||||||
type="text"
|
|
||||||
value={props.data[item.value]}
|
|
||||||
style={{ maxWidth: "100%", width: "100%" }}
|
|
||||||
onChange={(e) => {
|
|
||||||
props.onChange(item.value, e.currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ControlParamItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <div key={item.value}>{element}</div>;
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getModelParamBasicData = (
|
|
||||||
columns: any[],
|
|
||||||
data: any,
|
|
||||||
clearText?: boolean,
|
|
||||||
) => {
|
|
||||||
const newParams: any = {};
|
|
||||||
columns.forEach((item: any) => {
|
|
||||||
if (clearText && ["text", "textarea", "number"].includes(item.type)) {
|
|
||||||
newParams[item.value] = item.default || "";
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
newParams[item.value] = data[item.value] || item.default || "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newParams;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getParams = (model: any, params: any) => {
|
|
||||||
return models.find((m) => m.value === model.value)?.params(params) || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SdPanel() {
|
|
||||||
const sdStore = useSdStore();
|
|
||||||
const currentModel = sdStore.currentModel;
|
|
||||||
const setCurrentModel = sdStore.setCurrentModel;
|
|
||||||
const params = sdStore.currentParams;
|
|
||||||
const setParams = sdStore.setCurrentParams;
|
|
||||||
|
|
||||||
const handleValueChange = (field: string, val: any) => {
|
|
||||||
setParams({
|
|
||||||
...params,
|
|
||||||
[field]: val,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleModelChange = (model: any) => {
|
|
||||||
setCurrentModel(model);
|
|
||||||
setParams(getModelParamBasicData(model.params({}), params));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ControlParamItem title={Locale.SdPanel.AIModel}>
|
|
||||||
<div className={styles["ai-models"]}>
|
|
||||||
{models.map((item) => {
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
text={item.name}
|
|
||||||
key={item.value}
|
|
||||||
type={currentModel.value == item.value ? "primary" : null}
|
|
||||||
shadow
|
|
||||||
onClick={() => handleModelChange(item)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</ControlParamItem>
|
|
||||||
<ControlParam
|
|
||||||
columns={getParams?.(currentModel, params) as any[]}
|
|
||||||
data={params}
|
|
||||||
onChange={handleValueChange}
|
|
||||||
></ControlParam>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { IconButton } from "@/app/components/button";
|
|
||||||
import GithubIcon from "@/app/icons/github.svg";
|
|
||||||
import SDIcon from "@/app/icons/sd.svg";
|
|
||||||
import ReturnIcon from "@/app/icons/return.svg";
|
|
||||||
import HistoryIcon from "@/app/icons/history.svg";
|
|
||||||
import Locale from "@/app/locales";
|
|
||||||
|
|
||||||
import { Path, REPO_URL } from "@/app/constant";
|
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import {
|
|
||||||
SideBarContainer,
|
|
||||||
SideBarBody,
|
|
||||||
SideBarHeader,
|
|
||||||
SideBarTail,
|
|
||||||
useDragSideBar,
|
|
||||||
useHotKey,
|
|
||||||
} from "@/app/components/sidebar";
|
|
||||||
|
|
||||||
import { getParams, getModelParamBasicData } from "./sd-panel";
|
|
||||||
import { useSdStore } from "@/app/store/sd";
|
|
||||||
import { showToast } from "@/app/components/ui-lib";
|
|
||||||
import { useMobileScreen } from "@/app/utils";
|
|
||||||
|
|
||||||
const SdPanel = dynamic(
|
|
||||||
async () => (await import("@/app/components/sd")).SdPanel,
|
|
||||||
{
|
|
||||||
loading: () => null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export function SideBar(props: { className?: string }) {
|
|
||||||
useHotKey();
|
|
||||||
const isMobileScreen = useMobileScreen();
|
|
||||||
const { onDragStart, shouldNarrow } = useDragSideBar();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const sdStore = useSdStore();
|
|
||||||
const currentModel = sdStore.currentModel;
|
|
||||||
const params = sdStore.currentParams;
|
|
||||||
const setParams = sdStore.setCurrentParams;
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
const columns = getParams?.(currentModel, params);
|
|
||||||
const reqParams: any = {};
|
|
||||||
for (let i = 0; i < columns.length; i++) {
|
|
||||||
const item = columns[i];
|
|
||||||
reqParams[item.value] = params[item.value] ?? null;
|
|
||||||
if (item.required) {
|
|
||||||
if (!reqParams[item.value]) {
|
|
||||||
showToast(Locale.SdPanel.ParamIsRequired(item.name));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let data: any = {
|
|
||||||
model: currentModel.value,
|
|
||||||
model_name: currentModel.name,
|
|
||||||
status: "wait",
|
|
||||||
params: reqParams,
|
|
||||||
created_at: new Date().toLocaleString(),
|
|
||||||
img_data: "",
|
|
||||||
};
|
|
||||||
sdStore.sendTask(data, () => {
|
|
||||||
setParams(getModelParamBasicData(columns, params, true));
|
|
||||||
navigate(Path.SdNew);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SideBarContainer
|
|
||||||
onDragStart={onDragStart}
|
|
||||||
shouldNarrow={shouldNarrow}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{isMobileScreen ? (
|
|
||||||
<div
|
|
||||||
className="window-header"
|
|
||||||
data-tauri-drag-region
|
|
||||||
style={{
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="window-actions">
|
|
||||||
<div className="window-action-button">
|
|
||||||
<IconButton
|
|
||||||
icon={<ReturnIcon />}
|
|
||||||
bordered
|
|
||||||
title={Locale.Sd.Actions.ReturnHome}
|
|
||||||
onClick={() => navigate(Path.Home)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SDIcon width={50} height={50} />
|
|
||||||
<div className="window-actions">
|
|
||||||
<div className="window-action-button">
|
|
||||||
<IconButton
|
|
||||||
icon={<HistoryIcon />}
|
|
||||||
bordered
|
|
||||||
title={Locale.Sd.Actions.History}
|
|
||||||
onClick={() => navigate(Path.SdNew)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SideBarHeader
|
|
||||||
title={
|
|
||||||
<IconButton
|
|
||||||
icon={<ReturnIcon />}
|
|
||||||
bordered
|
|
||||||
title={Locale.Sd.Actions.ReturnHome}
|
|
||||||
onClick={() => navigate(Path.Home)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
logo={<SDIcon width={38} height={"100%"} />}
|
|
||||||
></SideBarHeader>
|
|
||||||
)}
|
|
||||||
<SideBarBody>
|
|
||||||
<SdPanel />
|
|
||||||
</SideBarBody>
|
|
||||||
<SideBarTail
|
|
||||||
primaryAction={
|
|
||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
|
||||||
<IconButton icon={<GithubIcon />} shadow />
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
secondaryAction={
|
|
||||||
<IconButton
|
|
||||||
text={Locale.SdPanel.Submit}
|
|
||||||
type="primary"
|
|
||||||
shadow
|
|
||||||
onClick={handleSubmit}
|
|
||||||
></IconButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SideBarContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
.sd-img-list{
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
.sd-img-item{
|
|
||||||
width: 48%;
|
|
||||||
.sd-img-item-info{
|
|
||||||
flex:1;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
user-select: text;
|
|
||||||
p{
|
|
||||||
margin: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.line-1{
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pre-img{
|
|
||||||
display: flex;
|
|
||||||
width: 130px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--second);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.img{
|
|
||||||
width: 130px;
|
|
||||||
height: 130px;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .3s;
|
|
||||||
&:hover{
|
|
||||||
opacity: .7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:not(:last-child){
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
.sd-img-list{
|
|
||||||
.sd-img-item{
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
import chatStyles from "@/app/components/chat.module.scss";
|
|
||||||
import styles from "@/app/components/sd/sd.module.scss";
|
|
||||||
import homeStyles from "@/app/components/home.module.scss";
|
|
||||||
|
|
||||||
import { IconButton } from "@/app/components/button";
|
|
||||||
import ReturnIcon from "@/app/icons/return.svg";
|
|
||||||
import Locale from "@/app/locales";
|
|
||||||
import { Path } from "@/app/constant";
|
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import {
|
|
||||||
copyToClipboard,
|
|
||||||
getMessageTextContent,
|
|
||||||
useMobileScreen,
|
|
||||||
} from "@/app/utils";
|
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
|
||||||
import { useAppConfig } from "@/app/store";
|
|
||||||
import MinIcon from "@/app/icons/min.svg";
|
|
||||||
import MaxIcon from "@/app/icons/max.svg";
|
|
||||||
import { getClientConfig } from "@/app/config/client";
|
|
||||||
import { ChatAction } from "@/app/components/chat";
|
|
||||||
import DeleteIcon from "@/app/icons/clear.svg";
|
|
||||||
import CopyIcon from "@/app/icons/copy.svg";
|
|
||||||
import PromptIcon from "@/app/icons/prompt.svg";
|
|
||||||
import ResetIcon from "@/app/icons/reload.svg";
|
|
||||||
import { useSdStore } from "@/app/store/sd";
|
|
||||||
import LoadingIcon from "@/app/icons/three-dots.svg";
|
|
||||||
import ErrorIcon from "@/app/icons/delete.svg";
|
|
||||||
import SDIcon from "@/app/icons/sd.svg";
|
|
||||||
import { Property } from "csstype";
|
|
||||||
import {
|
|
||||||
showConfirm,
|
|
||||||
showImageModal,
|
|
||||||
showModal,
|
|
||||||
} from "@/app/components/ui-lib";
|
|
||||||
import { removeImage } from "@/app/utils/chat";
|
|
||||||
import { SideBar } from "./sd-sidebar";
|
|
||||||
import { WindowContent } from "@/app/components/home";
|
|
||||||
import { params } from "./sd-panel";
|
|
||||||
|
|
||||||
function getSdTaskStatus(item: any) {
|
|
||||||
let s: string;
|
|
||||||
let color: Property.Color | undefined = undefined;
|
|
||||||
switch (item.status) {
|
|
||||||
case "success":
|
|
||||||
s = Locale.Sd.Status.Success;
|
|
||||||
color = "green";
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
s = Locale.Sd.Status.Error;
|
|
||||||
color = "red";
|
|
||||||
break;
|
|
||||||
case "wait":
|
|
||||||
s = Locale.Sd.Status.Wait;
|
|
||||||
color = "yellow";
|
|
||||||
break;
|
|
||||||
case "running":
|
|
||||||
s = Locale.Sd.Status.Running;
|
|
||||||
color = "blue";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
s = item.status.toUpperCase();
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<p className={styles["line-1"]} title={item.error} style={{ color: color }}>
|
|
||||||
<span>
|
|
||||||
{Locale.Sd.Status.Name}: {s}
|
|
||||||
</span>
|
|
||||||
{item.status === "error" && (
|
|
||||||
<span
|
|
||||||
className="clickable"
|
|
||||||
onClick={() => {
|
|
||||||
showModal({
|
|
||||||
title: Locale.Sd.Detail,
|
|
||||||
children: (
|
|
||||||
<div style={{ color: color, userSelect: "text" }}>
|
|
||||||
{item.error}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
- {item.error}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Sd() {
|
|
||||||
const isMobileScreen = useMobileScreen();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
|
||||||
const clientConfig = useMemo(() => getClientConfig(), []);
|
|
||||||
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
|
|
||||||
const config = useAppConfig();
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
const sdStore = useSdStore();
|
|
||||||
const [sdImages, setSdImages] = useState(sdStore.draw);
|
|
||||||
const isSd = location.pathname === Path.Sd;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSdImages(sdStore.draw);
|
|
||||||
}, [sdStore.currentId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SideBar className={isSd ? homeStyles["sidebar-show"] : ""} />
|
|
||||||
<WindowContent>
|
|
||||||
<div className={chatStyles.chat} key={"1"}>
|
|
||||||
<div className="window-header" data-tauri-drag-region>
|
|
||||||
{isMobileScreen && (
|
|
||||||
<div className="window-actions">
|
|
||||||
<div className={"window-action-button"}>
|
|
||||||
<IconButton
|
|
||||||
icon={<ReturnIcon />}
|
|
||||||
bordered
|
|
||||||
title={Locale.Chat.Actions.ChatList}
|
|
||||||
onClick={() => navigate(Path.Sd)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`window-header-title ${chatStyles["chat-body-title"]}`}
|
|
||||||
>
|
|
||||||
<div className={`window-header-main-title`}>Stability AI</div>
|
|
||||||
<div className="window-header-sub-title">
|
|
||||||
{Locale.Sd.SubTitle(sdImages.length || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="window-actions">
|
|
||||||
{showMaxIcon && (
|
|
||||||
<div className="window-action-button">
|
|
||||||
<IconButton
|
|
||||||
aria={Locale.Chat.Actions.FullScreen}
|
|
||||||
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
|
||||||
bordered
|
|
||||||
onClick={() => {
|
|
||||||
config.update(
|
|
||||||
(config) => (config.tightBorder = !config.tightBorder),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isMobileScreen && <SDIcon width={50} height={50} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={chatStyles["chat-body"]} ref={scrollRef}>
|
|
||||||
<div className={styles["sd-img-list"]}>
|
|
||||||
{sdImages.length > 0 ? (
|
|
||||||
sdImages.map((item: any) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
className={styles["sd-img-item"]}
|
|
||||||
>
|
|
||||||
{item.status === "success" ? (
|
|
||||||
<img
|
|
||||||
className={styles["img"]}
|
|
||||||
src={item.img_data}
|
|
||||||
alt={item.id}
|
|
||||||
onClick={(e) =>
|
|
||||||
showImageModal(
|
|
||||||
item.img_data,
|
|
||||||
true,
|
|
||||||
isMobileScreen
|
|
||||||
? { width: "100%", height: "fit-content" }
|
|
||||||
: { maxWidth: "100%", maxHeight: "100%" },
|
|
||||||
isMobileScreen
|
|
||||||
? { width: "100%", height: "fit-content" }
|
|
||||||
: { width: "100%", height: "100%" },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : item.status === "error" ? (
|
|
||||||
<div className={styles["pre-img"]}>
|
|
||||||
<ErrorIcon />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={styles["pre-img"]}>
|
|
||||||
<LoadingIcon />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{ marginLeft: "10px" }}
|
|
||||||
className={styles["sd-img-item-info"]}
|
|
||||||
>
|
|
||||||
<p className={styles["line-1"]}>
|
|
||||||
{Locale.SdPanel.Prompt}:{" "}
|
|
||||||
<span
|
|
||||||
className="clickable"
|
|
||||||
title={item.params.prompt}
|
|
||||||
onClick={() => {
|
|
||||||
showModal({
|
|
||||||
title: Locale.Sd.Detail,
|
|
||||||
children: (
|
|
||||||
<div style={{ userSelect: "text" }}>
|
|
||||||
{item.params.prompt}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.params.prompt}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{Locale.SdPanel.AIModel}: {item.model_name}
|
|
||||||
</p>
|
|
||||||
{getSdTaskStatus(item)}
|
|
||||||
<p>{item.created_at}</p>
|
|
||||||
<div className={chatStyles["chat-message-actions"]}>
|
|
||||||
<div className={chatStyles["chat-input-actions"]}>
|
|
||||||
<ChatAction
|
|
||||||
text={Locale.Sd.Actions.Params}
|
|
||||||
icon={<PromptIcon />}
|
|
||||||
onClick={() => {
|
|
||||||
showModal({
|
|
||||||
title: Locale.Sd.GenerateParams,
|
|
||||||
children: (
|
|
||||||
<div style={{ userSelect: "text" }}>
|
|
||||||
{Object.keys(item.params).map((key) => {
|
|
||||||
let label = key;
|
|
||||||
let value = item.params[key];
|
|
||||||
switch (label) {
|
|
||||||
case "prompt":
|
|
||||||
label = Locale.SdPanel.Prompt;
|
|
||||||
break;
|
|
||||||
case "negative_prompt":
|
|
||||||
label =
|
|
||||||
Locale.SdPanel.NegativePrompt;
|
|
||||||
break;
|
|
||||||
case "aspect_ratio":
|
|
||||||
label = Locale.SdPanel.AspectRatio;
|
|
||||||
break;
|
|
||||||
case "seed":
|
|
||||||
label = "Seed";
|
|
||||||
value = value || 0;
|
|
||||||
break;
|
|
||||||
case "output_format":
|
|
||||||
label = Locale.SdPanel.OutFormat;
|
|
||||||
value = value?.toUpperCase();
|
|
||||||
break;
|
|
||||||
case "style":
|
|
||||||
label = Locale.SdPanel.ImageStyle;
|
|
||||||
value = params
|
|
||||||
.find(
|
|
||||||
(item) =>
|
|
||||||
item.value === "style",
|
|
||||||
)
|
|
||||||
?.options?.find(
|
|
||||||
(item) => item.value === value,
|
|
||||||
)?.name;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
style={{ margin: "10px" }}
|
|
||||||
>
|
|
||||||
<strong>{label}: </strong>
|
|
||||||
{value}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ChatAction
|
|
||||||
text={Locale.Sd.Actions.Copy}
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(
|
|
||||||
getMessageTextContent({
|
|
||||||
role: "user",
|
|
||||||
content: item.params.prompt,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ChatAction
|
|
||||||
text={Locale.Sd.Actions.Retry}
|
|
||||||
icon={<ResetIcon />}
|
|
||||||
onClick={() => {
|
|
||||||
const reqData = {
|
|
||||||
model: item.model,
|
|
||||||
model_name: item.model_name,
|
|
||||||
status: "wait",
|
|
||||||
params: { ...item.params },
|
|
||||||
created_at: new Date().toLocaleString(),
|
|
||||||
img_data: "",
|
|
||||||
};
|
|
||||||
sdStore.sendTask(reqData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ChatAction
|
|
||||||
text={Locale.Sd.Actions.Delete}
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
onClick={async () => {
|
|
||||||
if (
|
|
||||||
await showConfirm(Locale.Sd.Danger.Delete)
|
|
||||||
) {
|
|
||||||
// remove img_data + remove item in list
|
|
||||||
removeImage(item.img_data).finally(() => {
|
|
||||||
sdStore.draw = sdImages.filter(
|
|
||||||
(i: any) => i.id !== item.id,
|
|
||||||
);
|
|
||||||
sdStore.getNextId();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div>{Locale.Sd.EmptyRecord}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</WindowContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useMemo, useState, Fragment } from "react";
|
import { useEffect, useRef, useMemo } from "react";
|
||||||
|
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@ import AddIcon from "../icons/add.svg";
|
|||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import MaskIcon from "../icons/mask.svg";
|
import MaskIcon from "../icons/mask.svg";
|
||||||
|
import PluginIcon from "../icons/plugin.svg";
|
||||||
import DragIcon from "../icons/drag.svg";
|
import DragIcon from "../icons/drag.svg";
|
||||||
import DiscoveryIcon from "../icons/discovery.svg";
|
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
|
||||||
@@ -23,20 +23,19 @@ import {
|
|||||||
MIN_SIDEBAR_WIDTH,
|
MIN_SIDEBAR_WIDTH,
|
||||||
NARROW_SIDEBAR_WIDTH,
|
NARROW_SIDEBAR_WIDTH,
|
||||||
Path,
|
Path,
|
||||||
PLUGINS,
|
|
||||||
REPO_URL,
|
REPO_URL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { isIOS, useMobileScreen } from "../utils";
|
import { isIOS, useMobileScreen } from "../utils";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { showConfirm, Selector } from "./ui-lib";
|
import { showConfirm, showToast } from "./ui-lib";
|
||||||
|
|
||||||
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
||||||
loading: () => null,
|
loading: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function useHotKey() {
|
function useHotKey() {
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -55,7 +54,7 @@ export function useHotKey() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDragSideBar() {
|
function useDragSideBar() {
|
||||||
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
||||||
|
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
@@ -128,21 +127,25 @@ export function useDragSideBar() {
|
|||||||
shouldNarrow,
|
shouldNarrow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function SideBarContainer(props: {
|
|
||||||
children: React.ReactNode;
|
export function SideBar(props: { className?: string }) {
|
||||||
onDragStart: (e: MouseEvent) => void;
|
const chatStore = useChatStore();
|
||||||
shouldNarrow: boolean;
|
|
||||||
className?: string;
|
// drag side bar
|
||||||
}) {
|
const { onDragStart, shouldNarrow } = useDragSideBar();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const config = useAppConfig();
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
const isIOSMobile = useMemo(
|
const isIOSMobile = useMemo(
|
||||||
() => isIOS() && isMobileScreen,
|
() => isIOS() && isMobileScreen,
|
||||||
[isMobileScreen],
|
[isMobileScreen],
|
||||||
);
|
);
|
||||||
const { children, className, onDragStart, shouldNarrow } = props;
|
|
||||||
|
useHotKey();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.sidebar} ${className} ${
|
className={`${styles.sidebar} ${props.className} ${
|
||||||
shouldNarrow && styles["narrow-sidebar"]
|
shouldNarrow && styles["narrow-sidebar"]
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
@@ -150,130 +153,43 @@ export function SideBarContainer(props: {
|
|||||||
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
|
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
|
||||||
<div
|
|
||||||
className={styles["sidebar-drag"]}
|
|
||||||
onPointerDown={(e) => onDragStart(e as any)}
|
|
||||||
>
|
|
||||||
<DragIcon />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SideBarHeader(props: {
|
|
||||||
title?: string | React.ReactNode;
|
|
||||||
subTitle?: string | React.ReactNode;
|
|
||||||
logo?: React.ReactNode;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const { title, subTitle, logo, children } = props;
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
||||||
<div className={styles["sidebar-title-container"]}>
|
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
||||||
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
NextChat
|
||||||
{title}
|
</div>
|
||||||
</div>
|
<div className={styles["sidebar-sub-title"]}>
|
||||||
<div className={styles["sidebar-sub-title"]}>{subTitle}</div>
|
Build your own AI assistant.
|
||||||
|
</div>
|
||||||
|
<div className={styles["sidebar-logo"] + " no-dark"}>
|
||||||
|
<ChatGptIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["sidebar-logo"] + " no-dark"}>{logo}</div>
|
|
||||||
</div>
|
</div>
|
||||||
{children}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SideBarBody(props: {
|
<div className={styles["sidebar-header-bar"]}>
|
||||||
children: React.ReactNode;
|
<IconButton
|
||||||
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
icon={<MaskIcon />}
|
||||||
}) {
|
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
||||||
const { onClick, children } = props;
|
className={styles["sidebar-bar-button"]}
|
||||||
return (
|
onClick={() => {
|
||||||
<div className={styles["sidebar-body"]} onClick={onClick}>
|
if (config.dontShowMaskSplashScreen !== true) {
|
||||||
{children}
|
navigate(Path.NewChat, { state: { fromHome: true } });
|
||||||
</div>
|
} else {
|
||||||
);
|
navigate(Path.Masks, { state: { fromHome: true } });
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
shadow
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={<PluginIcon />}
|
||||||
|
text={shouldNarrow ? undefined : Locale.Plugin.Name}
|
||||||
|
className={styles["sidebar-bar-button"]}
|
||||||
|
onClick={() => showToast(Locale.WIP)}
|
||||||
|
shadow
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
export function SideBarTail(props: {
|
<div
|
||||||
primaryAction?: React.ReactNode;
|
className={styles["sidebar-body"]}
|
||||||
secondaryAction?: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const { primaryAction, secondaryAction } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles["sidebar-tail"]}>
|
|
||||||
<div className={styles["sidebar-actions"]}>{primaryAction}</div>
|
|
||||||
<div className={styles["sidebar-actions"]}>{secondaryAction}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SideBar(props: { className?: string }) {
|
|
||||||
useHotKey();
|
|
||||||
const { onDragStart, shouldNarrow } = useDragSideBar();
|
|
||||||
const [showPluginSelector, setShowPluginSelector] = useState(false);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const config = useAppConfig();
|
|
||||||
const chatStore = useChatStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SideBarContainer
|
|
||||||
onDragStart={onDragStart}
|
|
||||||
shouldNarrow={shouldNarrow}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SideBarHeader
|
|
||||||
title="NextChat"
|
|
||||||
subTitle="Build your own AI assistant."
|
|
||||||
logo={<ChatGptIcon />}
|
|
||||||
>
|
|
||||||
<div className={styles["sidebar-header-bar"]}>
|
|
||||||
<IconButton
|
|
||||||
icon={<MaskIcon />}
|
|
||||||
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
|
||||||
className={styles["sidebar-bar-button"]}
|
|
||||||
onClick={() => {
|
|
||||||
if (config.dontShowMaskSplashScreen !== true) {
|
|
||||||
navigate(Path.NewChat, { state: { fromHome: true } });
|
|
||||||
} else {
|
|
||||||
navigate(Path.Masks, { state: { fromHome: true } });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
shadow
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon={<DiscoveryIcon />}
|
|
||||||
text={shouldNarrow ? undefined : Locale.Discovery.Name}
|
|
||||||
className={styles["sidebar-bar-button"]}
|
|
||||||
onClick={() => setShowPluginSelector(true)}
|
|
||||||
shadow
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{showPluginSelector && (
|
|
||||||
<Selector
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
title: "👇 Please select the plugin you need to use",
|
|
||||||
value: "-",
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
...PLUGINS.map((item) => {
|
|
||||||
return {
|
|
||||||
title: item.name,
|
|
||||||
value: item.path,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
onClose={() => setShowPluginSelector(false)}
|
|
||||||
onSelection={(s) => {
|
|
||||||
navigate(s[0], { state: { fromHome: true } });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SideBarHeader>
|
|
||||||
<SideBarBody
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
navigate(Path.Home);
|
navigate(Path.Home);
|
||||||
@@ -281,41 +197,32 @@ export function SideBar(props: { className?: string }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChatList narrow={shouldNarrow} />
|
<ChatList narrow={shouldNarrow} />
|
||||||
</SideBarBody>
|
</div>
|
||||||
<SideBarTail
|
|
||||||
primaryAction={
|
<div className={styles["sidebar-tail"]}>
|
||||||
<>
|
<div className={styles["sidebar-actions"]}>
|
||||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (await showConfirm(Locale.Home.DeleteChat)) {
|
if (await showConfirm(Locale.Home.DeleteChat)) {
|
||||||
chatStore.deleteSession(chatStore.currentSessionIndex);
|
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["sidebar-action"]}>
|
<div className={styles["sidebar-action"]}>
|
||||||
<Link to={Path.Settings}>
|
<Link to={Path.Settings}>
|
||||||
<IconButton
|
<IconButton icon={<SettingsIcon />} shadow />
|
||||||
aria={Locale.Settings.Title}
|
</Link>
|
||||||
icon={<SettingsIcon />}
|
</div>
|
||||||
shadow
|
<div className={styles["sidebar-action"]}>
|
||||||
/>
|
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||||
</Link>
|
<IconButton icon={<GithubIcon />} shadow />
|
||||||
</div>
|
</a>
|
||||||
<div className={styles["sidebar-action"]}>
|
</div>
|
||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
</div>
|
||||||
<IconButton
|
<div>
|
||||||
aria={Locale.Export.MessageFromChatGPT}
|
|
||||||
icon={<GithubIcon />}
|
|
||||||
shadow
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
secondaryAction={
|
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
||||||
@@ -329,8 +236,15 @@ export function SideBar(props: { className?: string }) {
|
|||||||
}}
|
}}
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
}
|
</div>
|
||||||
/>
|
</div>
|
||||||
</SideBarContainer>
|
|
||||||
|
<div
|
||||||
|
className={styles["sidebar-drag"]}
|
||||||
|
onPointerDown={(e) => onDragStart(e as any)}
|
||||||
|
>
|
||||||
|
<DragIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,19 +61,6 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical{
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
.list-header{
|
|
||||||
.list-item-title{
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.list-item-sub-title{
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
@@ -304,12 +291,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
.selector-item-disabled{
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
min-width: 300px;
|
|
||||||
.list {
|
.list {
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|||||||
@@ -13,15 +13,7 @@ import MinIcon from "../icons/min.svg";
|
|||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import React, {
|
import React, { HTMLProps, useEffect, useState } from "react";
|
||||||
CSSProperties,
|
|
||||||
HTMLProps,
|
|
||||||
MouseEvent,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useCallback,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
|
|
||||||
export function Popover(props: {
|
export function Popover(props: {
|
||||||
@@ -55,16 +47,11 @@ export function ListItem(props: {
|
|||||||
children?: JSX.Element | JSX.Element[];
|
children?: JSX.Element | JSX.Element[];
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: (e: MouseEvent) => void;
|
onClick?: () => void;
|
||||||
vertical?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={styles["list-item"] + ` ${props.className || ""}`}
|
||||||
styles["list-item"] +
|
|
||||||
` ${props.vertical ? styles["vertical"] : ""} ` +
|
|
||||||
` ${props.className || ""}`
|
|
||||||
}
|
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
<div className={styles["list-header"]}>
|
<div className={styles["list-header"]}>
|
||||||
@@ -265,10 +252,9 @@ export function Input(props: InputProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PasswordInput(
|
export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
|
||||||
props: HTMLProps<HTMLInputElement> & { aria?: string },
|
|
||||||
) {
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
function changeVisibility() {
|
function changeVisibility() {
|
||||||
setVisible(!visible);
|
setVisible(!visible);
|
||||||
}
|
}
|
||||||
@@ -276,7 +262,6 @@ export function PasswordInput(
|
|||||||
return (
|
return (
|
||||||
<div className={"password-input-container"}>
|
<div className={"password-input-container"}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria={props.aria}
|
|
||||||
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
|
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
|
||||||
onClick={changeVisibility}
|
onClick={changeVisibility}
|
||||||
className={"password-eye"}
|
className={"password-eye"}
|
||||||
@@ -435,25 +420,17 @@ export function showPrompt(content: any, value = "", rows = 3) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showImageModal(
|
export function showImageModal(img: string) {
|
||||||
img: string,
|
|
||||||
defaultMax?: boolean,
|
|
||||||
style?: CSSProperties,
|
|
||||||
boxStyle?: CSSProperties,
|
|
||||||
) {
|
|
||||||
showModal({
|
showModal({
|
||||||
title: Locale.Export.Image.Modal,
|
title: Locale.Export.Image.Modal,
|
||||||
defaultMax: defaultMax,
|
|
||||||
children: (
|
children: (
|
||||||
<div style={{ display: "flex", justifyContent: "center", ...boxStyle }}>
|
<div>
|
||||||
<img
|
<img
|
||||||
src={img}
|
src={img}
|
||||||
alt="preview"
|
alt="preview"
|
||||||
style={
|
style={{
|
||||||
style ?? {
|
maxWidth: "100%",
|
||||||
maxWidth: "100%",
|
}}
|
||||||
}
|
|
||||||
}
|
|
||||||
></img>
|
></img>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -465,56 +442,27 @@ export function Selector<T>(props: {
|
|||||||
title: string;
|
title: string;
|
||||||
subTitle?: string;
|
subTitle?: string;
|
||||||
value: T;
|
value: T;
|
||||||
disable?: boolean;
|
|
||||||
}>;
|
}>;
|
||||||
defaultSelectedValue?: T[] | T;
|
defaultSelectedValue?: T;
|
||||||
onSelection?: (selection: T[]) => void;
|
onSelection?: (selection: T[]) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [selectedValues, setSelectedValues] = useState<T[]>(
|
|
||||||
Array.isArray(props.defaultSelectedValue)
|
|
||||||
? props.defaultSelectedValue
|
|
||||||
: props.defaultSelectedValue !== undefined
|
|
||||||
? [props.defaultSelectedValue]
|
|
||||||
: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelection = (e: MouseEvent, value: T) => {
|
|
||||||
if (props.multiple) {
|
|
||||||
e.stopPropagation();
|
|
||||||
const newSelectedValues = selectedValues.includes(value)
|
|
||||||
? selectedValues.filter((v) => v !== value)
|
|
||||||
: [...selectedValues, value];
|
|
||||||
setSelectedValues(newSelectedValues);
|
|
||||||
props.onSelection?.(newSelectedValues);
|
|
||||||
} else {
|
|
||||||
setSelectedValues([value]);
|
|
||||||
props.onSelection?.([value]);
|
|
||||||
props.onClose?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
|
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
|
||||||
<div className={styles["selector-content"]}>
|
<div className={styles["selector-content"]}>
|
||||||
<List>
|
<List>
|
||||||
{props.items.map((item, i) => {
|
{props.items.map((item, i) => {
|
||||||
const selected = selectedValues.includes(item.value);
|
const selected = props.defaultSelectedValue === item.value;
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
className={`${styles["selector-item"]} ${
|
className={styles["selector-item"]}
|
||||||
item.disable && styles["selector-item-disabled"]
|
|
||||||
}`}
|
|
||||||
key={i}
|
key={i}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
subTitle={item.subTitle}
|
subTitle={item.subTitle}
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
if (item.disable) {
|
props.onSelection?.([item.value]);
|
||||||
e.stopPropagation();
|
props.onClose?.();
|
||||||
} else {
|
|
||||||
handleSelection(e, item.value);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selected ? (
|
{selected ? (
|
||||||
@@ -537,38 +485,3 @@ export function Selector<T>(props: {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function FullScreen(props: any) {
|
|
||||||
const { children, right = 10, top = 10, ...rest } = props;
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
|
||||||
const [fullScreen, setFullScreen] = useState(false);
|
|
||||||
const toggleFullscreen = useCallback(() => {
|
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
ref.current?.requestFullscreen();
|
|
||||||
} else {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScreenChange = (e: any) => {
|
|
||||||
if (e.target === ref.current) {
|
|
||||||
setFullScreen(!!document.fullscreenElement);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("fullscreenchange", handleScreenChange);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("fullscreenchange", handleScreenChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div ref={ref} style={{ position: "relative" }} {...rest}>
|
|
||||||
<div style={{ position: "absolute", right, top }}>
|
|
||||||
<IconButton
|
|
||||||
icon={fullScreen ? <MinIcon /> : <MaxIcon />}
|
|
||||||
onClick={toggleFullscreen}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import tauriConfig from "../../src-tauri/tauri.conf.json";
|
import tauriConfig from "../../src-tauri/tauri.conf.json";
|
||||||
import { DEFAULT_INPUT_TEMPLATE } from "../constant";
|
|
||||||
|
|
||||||
export const getBuildConfig = () => {
|
export const getBuildConfig = () => {
|
||||||
if (typeof process === "undefined") {
|
if (typeof process === "undefined") {
|
||||||
@@ -39,7 +38,6 @@ export const getBuildConfig = () => {
|
|||||||
...commitInfo,
|
...commitInfo,
|
||||||
buildMode,
|
buildMode,
|
||||||
isApp,
|
isApp,
|
||||||
template: process.env.DEFAULT_INPUT_TEMPLATE ?? DEFAULT_INPUT_TEMPLATE,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { BuildConfig, getBuildConfig } from "./build";
|
|||||||
export function getClientConfig() {
|
export function getClientConfig() {
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
// client side
|
// client side
|
||||||
return JSON.parse(queryMeta("config") || "{}") as BuildConfig;
|
return JSON.parse(queryMeta("config")) as BuildConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof process !== "undefined") {
|
if (typeof process !== "undefined") {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import md5 from "spark-md5";
|
import md5 from "spark-md5";
|
||||||
import { DEFAULT_MODELS, DEFAULT_GA_ID } from "../constant";
|
import { DEFAULT_MODELS } from "../constant";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
@@ -21,11 +21,6 @@ declare global {
|
|||||||
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
|
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
|
||||||
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
|
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
|
||||||
CUSTOM_MODELS?: string; // to control custom models
|
CUSTOM_MODELS?: string; // to control custom models
|
||||||
DEFAULT_MODEL?: string; // to control default model in every new chat window
|
|
||||||
|
|
||||||
// stability only
|
|
||||||
STABILITY_URL?: string;
|
|
||||||
STABILITY_API_KEY?: string;
|
|
||||||
|
|
||||||
// azure only
|
// azure only
|
||||||
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
|
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
|
||||||
@@ -38,41 +33,6 @@ declare global {
|
|||||||
|
|
||||||
// google tag manager
|
// google tag manager
|
||||||
GTM_ID?: string;
|
GTM_ID?: string;
|
||||||
|
|
||||||
// anthropic only
|
|
||||||
ANTHROPIC_URL?: string;
|
|
||||||
ANTHROPIC_API_KEY?: string;
|
|
||||||
ANTHROPIC_API_VERSION?: string;
|
|
||||||
|
|
||||||
// baidu only
|
|
||||||
BAIDU_URL?: string;
|
|
||||||
BAIDU_API_KEY?: string;
|
|
||||||
BAIDU_SECRET_KEY?: string;
|
|
||||||
|
|
||||||
// bytedance only
|
|
||||||
BYTEDANCE_URL?: string;
|
|
||||||
BYTEDANCE_API_KEY?: string;
|
|
||||||
|
|
||||||
// alibaba only
|
|
||||||
ALIBABA_URL?: string;
|
|
||||||
ALIBABA_API_KEY?: string;
|
|
||||||
|
|
||||||
// tencent only
|
|
||||||
TENCENT_URL?: string;
|
|
||||||
TENCENT_SECRET_KEY?: string;
|
|
||||||
TENCENT_SECRET_ID?: string;
|
|
||||||
|
|
||||||
// moonshot only
|
|
||||||
MOONSHOT_URL?: string;
|
|
||||||
MOONSHOT_API_KEY?: string;
|
|
||||||
|
|
||||||
// iflytek only
|
|
||||||
IFLYTEK_URL?: string;
|
|
||||||
IFLYTEK_API_KEY?: string;
|
|
||||||
IFLYTEK_API_SECRET?: string;
|
|
||||||
|
|
||||||
// custom template for preprocessing user input
|
|
||||||
DEFAULT_INPUT_TEMPLATE?: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,22 +50,6 @@ const ACCESS_CODES = (function getAccessCodes(): Set<string> {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function getApiKey(keys?: string) {
|
|
||||||
const apiKeyEnvVar = keys ?? "";
|
|
||||||
const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
|
|
||||||
const randomIndex = Math.floor(Math.random() * apiKeys.length);
|
|
||||||
const apiKey = apiKeys[randomIndex];
|
|
||||||
if (apiKey) {
|
|
||||||
console.log(
|
|
||||||
`[Server Config] using ${randomIndex + 1} of ${
|
|
||||||
apiKeys.length
|
|
||||||
} api key - ${apiKey}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getServerSideConfig = () => {
|
export const getServerSideConfig = () => {
|
||||||
if (typeof process === "undefined") {
|
if (typeof process === "undefined") {
|
||||||
throw Error(
|
throw Error(
|
||||||
@@ -115,103 +59,46 @@ export const getServerSideConfig = () => {
|
|||||||
|
|
||||||
const disableGPT4 = !!process.env.DISABLE_GPT4;
|
const disableGPT4 = !!process.env.DISABLE_GPT4;
|
||||||
let customModels = process.env.CUSTOM_MODELS ?? "";
|
let customModels = process.env.CUSTOM_MODELS ?? "";
|
||||||
let defaultModel = process.env.DEFAULT_MODEL ?? "";
|
|
||||||
|
|
||||||
if (disableGPT4) {
|
if (disableGPT4) {
|
||||||
if (customModels) customModels += ",";
|
if (customModels) customModels += ",";
|
||||||
customModels += DEFAULT_MODELS.filter(
|
customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4"))
|
||||||
(m) => m.name.startsWith("gpt-4") && !m.name.startsWith("gpt-4o-mini"),
|
|
||||||
)
|
|
||||||
.map((m) => "-" + m.name)
|
.map((m) => "-" + m.name)
|
||||||
.join(",");
|
.join(",");
|
||||||
if (
|
|
||||||
defaultModel.startsWith("gpt-4") &&
|
|
||||||
!defaultModel.startsWith("gpt-4o-mini")
|
|
||||||
)
|
|
||||||
defaultModel = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStability = !!process.env.STABILITY_API_KEY;
|
|
||||||
|
|
||||||
const isAzure = !!process.env.AZURE_URL;
|
const isAzure = !!process.env.AZURE_URL;
|
||||||
const isGoogle = !!process.env.GOOGLE_API_KEY;
|
const isGoogle = !!process.env.GOOGLE_API_KEY;
|
||||||
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
||||||
const isTencent = !!process.env.TENCENT_API_KEY;
|
|
||||||
|
|
||||||
const isBaidu = !!process.env.BAIDU_API_KEY;
|
const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
|
||||||
const isBytedance = !!process.env.BYTEDANCE_API_KEY;
|
const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
|
||||||
const isAlibaba = !!process.env.ALIBABA_API_KEY;
|
const randomIndex = Math.floor(Math.random() * apiKeys.length);
|
||||||
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
|
const apiKey = apiKeys[randomIndex];
|
||||||
const isIflytek = !!process.env.IFLYTEK_API_KEY;
|
console.log(
|
||||||
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
|
`[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
||||||
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
|
);
|
||||||
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
|
|
||||||
// const apiKey = apiKeys[randomIndex];
|
|
||||||
// console.log(
|
|
||||||
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
|
||||||
// );
|
|
||||||
|
|
||||||
const allowedWebDevEndpoints = (
|
|
||||||
process.env.WHITE_WEBDEV_ENDPOINTS ?? ""
|
|
||||||
).split(",");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseUrl: process.env.BASE_URL,
|
baseUrl: process.env.BASE_URL,
|
||||||
apiKey: getApiKey(process.env.OPENAI_API_KEY),
|
apiKey,
|
||||||
openaiOrgId: process.env.OPENAI_ORG_ID,
|
openaiOrgId: process.env.OPENAI_ORG_ID,
|
||||||
|
|
||||||
isStability,
|
|
||||||
stabilityUrl: process.env.STABILITY_URL,
|
|
||||||
stabilityApiKey: getApiKey(process.env.STABILITY_API_KEY),
|
|
||||||
|
|
||||||
isAzure,
|
isAzure,
|
||||||
azureUrl: process.env.AZURE_URL,
|
azureUrl: process.env.AZURE_URL,
|
||||||
azureApiKey: getApiKey(process.env.AZURE_API_KEY),
|
azureApiKey: process.env.AZURE_API_KEY,
|
||||||
azureApiVersion: process.env.AZURE_API_VERSION,
|
azureApiVersion: process.env.AZURE_API_VERSION,
|
||||||
|
|
||||||
isGoogle,
|
isGoogle,
|
||||||
googleApiKey: getApiKey(process.env.GOOGLE_API_KEY),
|
googleApiKey: process.env.GOOGLE_API_KEY,
|
||||||
googleUrl: process.env.GOOGLE_URL,
|
googleUrl: process.env.GOOGLE_URL,
|
||||||
|
|
||||||
isAnthropic,
|
isAnthropic,
|
||||||
anthropicApiKey: getApiKey(process.env.ANTHROPIC_API_KEY),
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
||||||
anthropicApiVersion: process.env.ANTHROPIC_API_VERSION,
|
anthropicApiVersion: process.env.ANTHROPIC_API_VERSION,
|
||||||
anthropicUrl: process.env.ANTHROPIC_URL,
|
anthropicUrl: process.env.ANTHROPIC_URL,
|
||||||
|
|
||||||
isBaidu,
|
|
||||||
baiduUrl: process.env.BAIDU_URL,
|
|
||||||
baiduApiKey: getApiKey(process.env.BAIDU_API_KEY),
|
|
||||||
baiduSecretKey: process.env.BAIDU_SECRET_KEY,
|
|
||||||
|
|
||||||
isBytedance,
|
|
||||||
bytedanceApiKey: getApiKey(process.env.BYTEDANCE_API_KEY),
|
|
||||||
bytedanceUrl: process.env.BYTEDANCE_URL,
|
|
||||||
|
|
||||||
isAlibaba,
|
|
||||||
alibabaUrl: process.env.ALIBABA_URL,
|
|
||||||
alibabaApiKey: getApiKey(process.env.ALIBABA_API_KEY),
|
|
||||||
|
|
||||||
isTencent,
|
|
||||||
tencentUrl: process.env.TENCENT_URL,
|
|
||||||
tencentSecretKey: getApiKey(process.env.TENCENT_SECRET_KEY),
|
|
||||||
tencentSecretId: process.env.TENCENT_SECRET_ID,
|
|
||||||
|
|
||||||
isMoonshot,
|
|
||||||
moonshotUrl: process.env.MOONSHOT_URL,
|
|
||||||
moonshotApiKey: getApiKey(process.env.MOONSHOT_API_KEY),
|
|
||||||
|
|
||||||
isIflytek,
|
|
||||||
iflytekUrl: process.env.IFLYTEK_URL,
|
|
||||||
iflytekApiKey: process.env.IFLYTEK_API_KEY,
|
|
||||||
iflytekApiSecret: process.env.IFLYTEK_API_SECRET,
|
|
||||||
|
|
||||||
cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,
|
|
||||||
cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID,
|
|
||||||
cloudflareKVApiKey: getApiKey(process.env.CLOUDFLARE_KV_API_KEY),
|
|
||||||
cloudflareKVTTL: process.env.CLOUDFLARE_KV_TTL,
|
|
||||||
|
|
||||||
gtmId: process.env.GTM_ID,
|
gtmId: process.env.GTM_ID,
|
||||||
gaId: process.env.GA_ID || DEFAULT_GA_ID,
|
|
||||||
|
|
||||||
needCode: ACCESS_CODES.size > 0,
|
needCode: ACCESS_CODES.size > 0,
|
||||||
code: process.env.CODE,
|
code: process.env.CODE,
|
||||||
@@ -225,7 +112,5 @@ export const getServerSideConfig = () => {
|
|||||||
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
|
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
|
||||||
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
||||||
customModels,
|
customModels,
|
||||||
defaultModel,
|
|
||||||
allowedWebDevEndpoints,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
514
app/constant.ts
514
app/constant.ts
@@ -1,4 +1,4 @@
|
|||||||
export const OWNER = "ChatGPTNextWeb";
|
export const OWNER = "Yidadaa";
|
||||||
export const REPO = "ChatGPT-Next-Web";
|
export const REPO = "ChatGPT-Next-Web";
|
||||||
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
||||||
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
||||||
@@ -8,29 +8,12 @@ export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/c
|
|||||||
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
||||||
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
|
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
|
||||||
|
|
||||||
export const STABILITY_BASE_URL = "https://api.stability.ai";
|
|
||||||
|
|
||||||
export const DEFAULT_API_HOST = "https://api.nextchat.dev";
|
export const DEFAULT_API_HOST = "https://api.nextchat.dev";
|
||||||
export const OPENAI_BASE_URL = "https://api.openai.com";
|
export const OPENAI_BASE_URL = "https://api.openai.com";
|
||||||
export const ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
export const ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
||||||
|
|
||||||
export const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/";
|
export const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/";
|
||||||
|
|
||||||
export const BAIDU_BASE_URL = "https://aip.baidubce.com";
|
|
||||||
export const BAIDU_OATUH_URL = `${BAIDU_BASE_URL}/oauth/2.0/token`;
|
|
||||||
|
|
||||||
export const BYTEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com";
|
|
||||||
|
|
||||||
export const ALIBABA_BASE_URL = "https://dashscope.aliyuncs.com/api/";
|
|
||||||
|
|
||||||
export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com";
|
|
||||||
|
|
||||||
export const MOONSHOT_BASE_URL = "https://api.moonshot.cn";
|
|
||||||
export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
|
|
||||||
|
|
||||||
export const CACHE_URL_PREFIX = "/api/cache";
|
|
||||||
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
|
|
||||||
|
|
||||||
export enum Path {
|
export enum Path {
|
||||||
Home = "/",
|
Home = "/",
|
||||||
Chat = "/chat",
|
Chat = "/chat",
|
||||||
@@ -38,25 +21,12 @@ export enum Path {
|
|||||||
NewChat = "/new-chat",
|
NewChat = "/new-chat",
|
||||||
Masks = "/masks",
|
Masks = "/masks",
|
||||||
Auth = "/auth",
|
Auth = "/auth",
|
||||||
Sd = "/sd",
|
|
||||||
SdNew = "/sd-new",
|
|
||||||
Artifacts = "/artifacts",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApiPath {
|
export enum ApiPath {
|
||||||
Cors = "",
|
Cors = "",
|
||||||
Azure = "/api/azure",
|
|
||||||
OpenAI = "/api/openai",
|
OpenAI = "/api/openai",
|
||||||
Anthropic = "/api/anthropic",
|
Anthropic = "/api/anthropic",
|
||||||
Google = "/api/google",
|
|
||||||
Baidu = "/api/baidu",
|
|
||||||
ByteDance = "/api/bytedance",
|
|
||||||
Alibaba = "/api/alibaba",
|
|
||||||
Tencent = "/api/tencent",
|
|
||||||
Moonshot = "/api/moonshot",
|
|
||||||
Iflytek = "/api/iflytek",
|
|
||||||
Stability = "/api/stability",
|
|
||||||
Artifacts = "/api/artifacts",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SlotID {
|
export enum SlotID {
|
||||||
@@ -69,10 +39,6 @@ export enum FileName {
|
|||||||
Prompts = "prompts.json",
|
Prompts = "prompts.json",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Plugin {
|
|
||||||
Artifacts = "artifacts",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum StoreKey {
|
export enum StoreKey {
|
||||||
Chat = "chat-next-web-store",
|
Chat = "chat-next-web-store",
|
||||||
Access = "access-control",
|
Access = "access-control",
|
||||||
@@ -81,7 +47,6 @@ export enum StoreKey {
|
|||||||
Prompt = "prompt-store",
|
Prompt = "prompt-store",
|
||||||
Update = "chat-update",
|
Update = "chat-update",
|
||||||
Sync = "sync",
|
Sync = "sync",
|
||||||
SdList = "sd-list",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SIDEBAR_WIDTH = 300;
|
export const DEFAULT_SIDEBAR_WIDTH = 300;
|
||||||
@@ -105,42 +70,14 @@ export enum ServiceProvider {
|
|||||||
Azure = "Azure",
|
Azure = "Azure",
|
||||||
Google = "Google",
|
Google = "Google",
|
||||||
Anthropic = "Anthropic",
|
Anthropic = "Anthropic",
|
||||||
Baidu = "Baidu",
|
|
||||||
ByteDance = "ByteDance",
|
|
||||||
Alibaba = "Alibaba",
|
|
||||||
Tencent = "Tencent",
|
|
||||||
Moonshot = "Moonshot",
|
|
||||||
Stability = "Stability",
|
|
||||||
Iflytek = "Iflytek",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
|
|
||||||
// BLOCK_NONE will not block any content, and BLOCK_ONLY_HIGH will block only high-risk content.
|
|
||||||
export enum GoogleSafetySettingsThreshold {
|
|
||||||
BLOCK_NONE = "BLOCK_NONE",
|
|
||||||
BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH",
|
|
||||||
BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE",
|
|
||||||
BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ModelProvider {
|
export enum ModelProvider {
|
||||||
Stability = "Stability",
|
|
||||||
GPT = "GPT",
|
GPT = "GPT",
|
||||||
GeminiPro = "GeminiPro",
|
GeminiPro = "GeminiPro",
|
||||||
Claude = "Claude",
|
Claude = "Claude",
|
||||||
Ernie = "Ernie",
|
|
||||||
Doubao = "Doubao",
|
|
||||||
Qwen = "Qwen",
|
|
||||||
Hunyuan = "Hunyuan",
|
|
||||||
Moonshot = "Moonshot",
|
|
||||||
Iflytek = "Iflytek",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Stability = {
|
|
||||||
GeneratePath: "v2beta/stable-image/generate",
|
|
||||||
ExampleEndpoint: "https://api.stability.ai",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Anthropic = {
|
export const Anthropic = {
|
||||||
ChatPath: "v1/messages",
|
ChatPath: "v1/messages",
|
||||||
ChatPath1: "v1/complete",
|
ChatPath1: "v1/complete",
|
||||||
@@ -150,329 +87,274 @@ export const Anthropic = {
|
|||||||
|
|
||||||
export const OpenaiPath = {
|
export const OpenaiPath = {
|
||||||
ChatPath: "v1/chat/completions",
|
ChatPath: "v1/chat/completions",
|
||||||
ImagePath: "v1/images/generations",
|
|
||||||
UsagePath: "dashboard/billing/usage",
|
UsagePath: "dashboard/billing/usage",
|
||||||
SubsPath: "dashboard/billing/subscription",
|
SubsPath: "dashboard/billing/subscription",
|
||||||
ListModelPath: "v1/models",
|
ListModelPath: "v1/models",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Azure = {
|
export const Azure = {
|
||||||
ChatPath: (deployName: string, apiVersion: string) =>
|
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
|
||||||
`deployments/${deployName}/chat/completions?api-version=${apiVersion}`,
|
|
||||||
// https://<your_resource_name>.openai.azure.com/openai/deployments/<your_deployment_name>/images/generations?api-version=<api_version>
|
|
||||||
ImagePath: (deployName: string, apiVersion: string) =>
|
|
||||||
`deployments/${deployName}/images/generations?api-version=${apiVersion}`,
|
|
||||||
ExampleEndpoint: "https://{resource-url}/openai",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Google = {
|
export const Google = {
|
||||||
ExampleEndpoint: "https://generativelanguage.googleapis.com/",
|
ExampleEndpoint: "https://generativelanguage.googleapis.com/",
|
||||||
ChatPath: (modelName: string) =>
|
ChatPath: "v1beta/models/gemini-pro:generateContent",
|
||||||
`v1beta/models/${modelName}:streamGenerateContent`,
|
VisionChatPath: "v1beta/models/gemini-pro-vision:generateContent",
|
||||||
};
|
|
||||||
|
|
||||||
export const Baidu = {
|
// /api/openai/v1/chat/completions
|
||||||
ExampleEndpoint: BAIDU_BASE_URL,
|
|
||||||
ChatPath: (modelName: string) => {
|
|
||||||
let endpoint = modelName;
|
|
||||||
if (modelName === "ernie-4.0-8k") {
|
|
||||||
endpoint = "completions_pro";
|
|
||||||
}
|
|
||||||
if (modelName === "ernie-4.0-8k-preview-0518") {
|
|
||||||
endpoint = "completions_adv_pro";
|
|
||||||
}
|
|
||||||
if (modelName === "ernie-3.5-8k") {
|
|
||||||
endpoint = "completions";
|
|
||||||
}
|
|
||||||
if (modelName === "ernie-speed-8k") {
|
|
||||||
endpoint = "ernie_speed";
|
|
||||||
}
|
|
||||||
return `rpc/2.0/ai_custom/v1/wenxinworkshop/chat/${endpoint}`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ByteDance = {
|
|
||||||
ExampleEndpoint: "https://ark.cn-beijing.volces.com/api/",
|
|
||||||
ChatPath: "api/v3/chat/completions",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Alibaba = {
|
|
||||||
ExampleEndpoint: ALIBABA_BASE_URL,
|
|
||||||
ChatPath: "v1/services/aigc/text-generation/generation",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Tencent = {
|
|
||||||
ExampleEndpoint: TENCENT_BASE_URL,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Moonshot = {
|
|
||||||
ExampleEndpoint: MOONSHOT_BASE_URL,
|
|
||||||
ChatPath: "v1/chat/completions",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Iflytek = {
|
|
||||||
ExampleEndpoint: IFLYTEK_BASE_URL,
|
|
||||||
ChatPath: "v1/chat/completions",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
|
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
|
||||||
// export const DEFAULT_SYSTEM_TEMPLATE = `
|
|
||||||
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
|
|
||||||
// Knowledge cutoff: {{cutoff}}
|
|
||||||
// Current model: {{model}}
|
|
||||||
// Current time: {{time}}
|
|
||||||
// Latex inline: $x^2$
|
|
||||||
// Latex block: $$e=mc^2$$
|
|
||||||
// `;
|
|
||||||
export const DEFAULT_SYSTEM_TEMPLATE = `
|
export const DEFAULT_SYSTEM_TEMPLATE = `
|
||||||
You are ChatGPT, a large language model trained by {{ServiceProvider}}.
|
You are ChatGPT, a large language model trained by {{ServiceProvider}}.
|
||||||
Knowledge cutoff: {{cutoff}}
|
Knowledge cutoff: {{cutoff}}
|
||||||
Current model: {{model}}
|
Current model: {{model}}
|
||||||
Current time: {{time}}
|
Current time: {{time}}
|
||||||
Latex inline: \\(x^2\\)
|
Latex inline: $x^2$
|
||||||
Latex block: $$e=mc^2$$
|
Latex block: $$e=mc^2$$
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SUMMARIZE_MODEL = "gpt-4o-mini";
|
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
|
||||||
export const GEMINI_SUMMARIZE_MODEL = "gemini-pro";
|
export const GEMINI_SUMMARIZE_MODEL = "gemini-pro";
|
||||||
|
|
||||||
export const KnowledgeCutOffDate: Record<string, string> = {
|
export const KnowledgeCutOffDate: Record<string, string> = {
|
||||||
default: "2021-09",
|
default: "2021-09",
|
||||||
"gpt-4-turbo": "2023-12",
|
|
||||||
"gpt-4-turbo-2024-04-09": "2023-12",
|
|
||||||
"gpt-4-turbo-preview": "2023-12",
|
"gpt-4-turbo-preview": "2023-12",
|
||||||
"gpt-4o": "2023-10",
|
"gpt-4-1106-preview": "2023-04",
|
||||||
"gpt-4o-2024-05-13": "2023-10",
|
"gpt-4-0125-preview": "2023-12",
|
||||||
"gpt-4o-2024-08-06": "2023-10",
|
|
||||||
"gpt-4o-mini": "2023-10",
|
|
||||||
"gpt-4o-mini-2024-07-18": "2023-10",
|
|
||||||
"gpt-4-vision-preview": "2023-04",
|
"gpt-4-vision-preview": "2023-04",
|
||||||
// After improvements,
|
// After improvements,
|
||||||
// it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously.
|
// it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously.
|
||||||
"gemini-pro": "2023-12",
|
"gemini-pro": "2023-12",
|
||||||
"gemini-pro-vision": "2023-12",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openaiModels = [
|
|
||||||
"gpt-3.5-turbo",
|
|
||||||
"gpt-3.5-turbo-1106",
|
|
||||||
"gpt-3.5-turbo-0125",
|
|
||||||
"gpt-4",
|
|
||||||
"gpt-4-0613",
|
|
||||||
"gpt-4-32k",
|
|
||||||
"gpt-4-32k-0613",
|
|
||||||
"gpt-4-turbo",
|
|
||||||
"gpt-4-turbo-preview",
|
|
||||||
"gpt-4o",
|
|
||||||
"gpt-4o-2024-05-13",
|
|
||||||
"gpt-4o-2024-08-06",
|
|
||||||
"gpt-4o-mini",
|
|
||||||
"gpt-4o-mini-2024-07-18",
|
|
||||||
"gpt-4-vision-preview",
|
|
||||||
"gpt-4-turbo-2024-04-09",
|
|
||||||
"gpt-4-1106-preview",
|
|
||||||
"dall-e-3",
|
|
||||||
];
|
|
||||||
|
|
||||||
const googleModels = [
|
|
||||||
"gemini-1.0-pro",
|
|
||||||
"gemini-1.5-pro-latest",
|
|
||||||
"gemini-1.5-flash-latest",
|
|
||||||
"gemini-pro-vision",
|
|
||||||
];
|
|
||||||
|
|
||||||
const anthropicModels = [
|
|
||||||
"claude-instant-1.2",
|
|
||||||
"claude-2.0",
|
|
||||||
"claude-2.1",
|
|
||||||
"claude-3-sonnet-20240229",
|
|
||||||
"claude-3-opus-20240229",
|
|
||||||
"claude-3-haiku-20240307",
|
|
||||||
"claude-3-5-sonnet-20240620",
|
|
||||||
];
|
|
||||||
|
|
||||||
const baiduModels = [
|
|
||||||
"ernie-4.0-turbo-8k",
|
|
||||||
"ernie-4.0-8k",
|
|
||||||
"ernie-4.0-8k-preview",
|
|
||||||
"ernie-4.0-8k-preview-0518",
|
|
||||||
"ernie-4.0-8k-latest",
|
|
||||||
"ernie-3.5-8k",
|
|
||||||
"ernie-3.5-8k-0205",
|
|
||||||
"ernie-speed-128k",
|
|
||||||
"ernie-speed-8k",
|
|
||||||
"ernie-lite-8k",
|
|
||||||
"ernie-tiny-8k",
|
|
||||||
];
|
|
||||||
|
|
||||||
const bytedanceModels = [
|
|
||||||
"Doubao-lite-4k",
|
|
||||||
"Doubao-lite-32k",
|
|
||||||
"Doubao-lite-128k",
|
|
||||||
"Doubao-pro-4k",
|
|
||||||
"Doubao-pro-32k",
|
|
||||||
"Doubao-pro-128k",
|
|
||||||
];
|
|
||||||
|
|
||||||
const alibabaModes = [
|
|
||||||
"qwen-turbo",
|
|
||||||
"qwen-plus",
|
|
||||||
"qwen-max",
|
|
||||||
"qwen-max-0428",
|
|
||||||
"qwen-max-0403",
|
|
||||||
"qwen-max-0107",
|
|
||||||
"qwen-max-longcontext",
|
|
||||||
];
|
|
||||||
|
|
||||||
const tencentModels = [
|
|
||||||
"hunyuan-pro",
|
|
||||||
"hunyuan-standard",
|
|
||||||
"hunyuan-lite",
|
|
||||||
"hunyuan-role",
|
|
||||||
"hunyuan-functioncall",
|
|
||||||
"hunyuan-code",
|
|
||||||
"hunyuan-vision",
|
|
||||||
];
|
|
||||||
|
|
||||||
const moonshotModes = ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"];
|
|
||||||
|
|
||||||
const iflytekModels = [
|
|
||||||
"general",
|
|
||||||
"generalv3",
|
|
||||||
"pro-128k",
|
|
||||||
"generalv3.5",
|
|
||||||
"4.0Ultra",
|
|
||||||
];
|
|
||||||
|
|
||||||
let seq = 1000; // 内置的模型序号生成器从1000开始
|
|
||||||
export const DEFAULT_MODELS = [
|
export const DEFAULT_MODELS = [
|
||||||
...openaiModels.map((name) => ({
|
{
|
||||||
name,
|
name: "gpt-4",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++, // Global sequence sort(index)
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "openai",
|
id: "openai",
|
||||||
providerName: "OpenAI",
|
providerName: "OpenAI",
|
||||||
providerType: "openai",
|
providerType: "openai",
|
||||||
sorted: 1, // 这里是固定的,确保顺序与之前内置的版本一致
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...openaiModels.map((name) => ({
|
{
|
||||||
name,
|
name: "gpt-4-0314",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "azure",
|
id: "openai",
|
||||||
providerName: "Azure",
|
providerName: "OpenAI",
|
||||||
providerType: "azure",
|
providerType: "openai",
|
||||||
sorted: 2,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...googleModels.map((name) => ({
|
{
|
||||||
name,
|
name: "gpt-4-0613",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-32k",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-32k-0314",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-32k-0613",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-turbo-preview",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-1106-preview",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-0125-preview",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-4-vision-preview",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-0125",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-0301",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-0613",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-1106",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-16k",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gpt-3.5-turbo-16k-0613",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "openai",
|
||||||
|
providerName: "OpenAI",
|
||||||
|
providerType: "openai",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gemini-pro",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "google",
|
id: "google",
|
||||||
providerName: "Google",
|
providerName: "Google",
|
||||||
providerType: "google",
|
providerType: "google",
|
||||||
sorted: 3,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...anthropicModels.map((name) => ({
|
{
|
||||||
name,
|
name: "gemini-pro-vision",
|
||||||
|
available: true,
|
||||||
|
provider: {
|
||||||
|
id: "google",
|
||||||
|
providerName: "Google",
|
||||||
|
providerType: "google",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "claude-instant-1.2",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "anthropic",
|
id: "anthropic",
|
||||||
providerName: "Anthropic",
|
providerName: "Anthropic",
|
||||||
providerType: "anthropic",
|
providerType: "anthropic",
|
||||||
sorted: 4,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...baiduModels.map((name) => ({
|
{
|
||||||
name,
|
name: "claude-2.0",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "baidu",
|
id: "anthropic",
|
||||||
providerName: "Baidu",
|
providerName: "Anthropic",
|
||||||
providerType: "baidu",
|
providerType: "anthropic",
|
||||||
sorted: 5,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...bytedanceModels.map((name) => ({
|
{
|
||||||
name,
|
name: "claude-2.1",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "bytedance",
|
id: "anthropic",
|
||||||
providerName: "ByteDance",
|
providerName: "Anthropic",
|
||||||
providerType: "bytedance",
|
providerType: "anthropic",
|
||||||
sorted: 6,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...alibabaModes.map((name) => ({
|
{
|
||||||
name,
|
name: "claude-3-opus-20240229",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "alibaba",
|
id: "anthropic",
|
||||||
providerName: "Alibaba",
|
providerName: "Anthropic",
|
||||||
providerType: "alibaba",
|
providerType: "anthropic",
|
||||||
sorted: 7,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...tencentModels.map((name) => ({
|
{
|
||||||
name,
|
name: "claude-3-sonnet-20240229",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "tencent",
|
id: "anthropic",
|
||||||
providerName: "Tencent",
|
providerName: "Anthropic",
|
||||||
providerType: "tencent",
|
providerType: "anthropic",
|
||||||
sorted: 8,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...moonshotModes.map((name) => ({
|
{
|
||||||
name,
|
name: "claude-3-haiku-20240307",
|
||||||
available: true,
|
available: true,
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
provider: {
|
||||||
id: "moonshot",
|
id: "anthropic",
|
||||||
providerName: "Moonshot",
|
providerName: "Anthropic",
|
||||||
providerType: "moonshot",
|
providerType: "anthropic",
|
||||||
sorted: 9,
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
...iflytekModels.map((name) => ({
|
|
||||||
name,
|
|
||||||
available: true,
|
|
||||||
sorted: seq++,
|
|
||||||
provider: {
|
|
||||||
id: "iflytek",
|
|
||||||
providerName: "Iflytek",
|
|
||||||
providerType: "iflytek",
|
|
||||||
sorted: 10,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const CHAT_PAGE_SIZE = 15;
|
export const CHAT_PAGE_SIZE = 15;
|
||||||
export const MAX_RENDER_MSG_COUNT = 45;
|
export const MAX_RENDER_MSG_COUNT = 45;
|
||||||
|
|
||||||
// some famous webdav endpoints
|
|
||||||
export const internalAllowedWebDavEndpoints = [
|
|
||||||
"https://dav.jianguoyun.com/dav/",
|
|
||||||
"https://dav.dropdav.com/",
|
|
||||||
"https://dav.box.com/dav",
|
|
||||||
"https://nanao.teracloud.jp/dav/",
|
|
||||||
"https://bora.teracloud.jp/dav/",
|
|
||||||
"https://webdav.4shared.com/",
|
|
||||||
"https://dav.idrivesync.com",
|
|
||||||
"https://webdav.yandex.com",
|
|
||||||
"https://app.koofr.net/dav/Koofr",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DEFAULT_GA_ID = "G-89WN60ZK2E";
|
|
||||||
export const PLUGINS = [{ name: "Stable Diffusion", path: Path.Sd }];
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2rem" height="1.2rem" viewBox="0 0 24 24">
|
|
||||||
<g fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="9" />
|
|
||||||
<path
|
|
||||||
d="M11.307 9.739L15 9l-.739 3.693a2 2 0 0 1-1.568 1.569L9 15l.739-3.693a2 2 0 0 1 1.568-1.568" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 371 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#333" class="bi bi-badge-hd" viewBox="0 0 16 16">
|
|
||||||
<path d="M7.396 11V5.001H6.209v2.44H3.687V5H2.5v6h1.187V8.43h2.522V11zM8.5 5.001V11h2.188c1.811 0 2.685-1.107 2.685-3.015 0-1.894-.86-2.984-2.684-2.984zm1.187.967h.843c1.112 0 1.622.686 1.622 2.04 0 1.353-.505 2.02-1.622 2.02h-.843z"/>
|
|
||||||
<path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 514 B |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M5.81836 6.72729V14H13.0911" stroke="#333" stroke-width="4" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" />
|
|
||||||
<path
|
|
||||||
d="M4 24C4 35.0457 12.9543 44 24 44V44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C16.598 4 10.1351 8.02111 6.67677 13.9981"
|
|
||||||
stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
<path d="M24.005 12L24.0038 24.0088L32.4832 32.4882" stroke="#333" stroke-width="4"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 660 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#333" class="bi bi-palette" viewBox="0 0 16 16">
|
|
||||||
<path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3m4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/>
|
|
||||||
<path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8m-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 781 B |
@@ -1,12 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.21em" height="1em" viewBox="0 0 256 213">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="logosStabilityAiIcon0" x1="50%" x2="50%" y1="0%" y2="100%">
|
|
||||||
<stop offset="0%" stop-color="#9d39ff" />
|
|
||||||
<stop offset="100%" stop-color="#a380ff" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#logosStabilityAiIcon0)"
|
|
||||||
d="M72.418 212.45c49.478 0 81.658-26.205 81.658-65.626c0-30.572-19.572-49.998-54.569-58.043l-22.469-6.74c-19.71-4.424-31.215-9.738-28.505-23.312c2.255-11.292 9.002-17.667 24.69-17.667c49.872 0 68.35 17.667 68.35 17.667V16.237S123.583 0 73.223 0C25.757 0 0 24.424 0 62.236c0 30.571 17.85 48.35 54.052 56.798q3.802.95 3.885.976q8.26 2.556 22.293 6.755c18.504 4.425 23.262 9.121 23.262 23.2c0 12.872-13.374 20.19-31.074 20.19C21.432 170.154 0 144.36 0 144.36v47.078s13.402 21.01 72.418 21.01" />
|
|
||||||
<path fill="#e80000"
|
|
||||||
d="M225.442 209.266c17.515 0 30.558-12.67 30.558-29.812c0-17.515-12.67-29.813-30.558-29.813c-17.515 0-30.185 12.298-30.185 29.813s12.67 29.812 30.185 29.812" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M42 7H6C4.89543 7 4 7.89543 4 9V39C4 40.1046 4.89543 41 6 41H42C43.1046 41 44 40.1046 44 39V9C44 7.89543 43.1046 7 42 7Z" fill="none" stroke="#333" stroke-width="4"/><path d="M30 30V18L38 30V18" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 30V18L18 30V18" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M24 20V21" stroke="#333" stroke-width="4" stroke-linecap="round"/><path d="M24 27V28" stroke="#333" stroke-width="4" stroke-linecap="round"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 681 B |
@@ -3,29 +3,28 @@ import "./styles/globals.scss";
|
|||||||
import "./styles/markdown.scss";
|
import "./styles/markdown.scss";
|
||||||
import "./styles/highlight.scss";
|
import "./styles/highlight.scss";
|
||||||
import { getClientConfig } from "./config/client";
|
import { getClientConfig } from "./config/client";
|
||||||
import type { Metadata, Viewport } from "next";
|
import { type Metadata } from "next";
|
||||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||||
import { getServerSideConfig } from "./config/server";
|
import { getServerSideConfig } from "./config/server";
|
||||||
import { GoogleTagManager, GoogleAnalytics } from "@next/third-parties/google";
|
import { GoogleTagManager } from "@next/third-parties/google";
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "NextChat",
|
title: "NextChat",
|
||||||
description: "Your personal ChatGPT Chat Bot.",
|
description: "Your personal ChatGPT Chat Bot.",
|
||||||
appleWebApp: {
|
viewport: {
|
||||||
title: "NextChat",
|
width: "device-width",
|
||||||
statusBarStyle: "default",
|
initialScale: 1,
|
||||||
|
maximumScale: 1,
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
|
||||||
width: "device-width",
|
|
||||||
initialScale: 1,
|
|
||||||
maximumScale: 1,
|
|
||||||
themeColor: [
|
themeColor: [
|
||||||
{ media: "(prefers-color-scheme: light)", color: "#fafafa" },
|
{ media: "(prefers-color-scheme: light)", color: "#fafafa" },
|
||||||
{ media: "(prefers-color-scheme: dark)", color: "#151515" },
|
{ media: "(prefers-color-scheme: dark)", color: "#151515" },
|
||||||
],
|
],
|
||||||
|
appleWebApp: {
|
||||||
|
title: "NextChat",
|
||||||
|
statusBarStyle: "default",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -37,10 +36,6 @@ export default function RootLayout({
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta name="config" content={JSON.stringify(getClientConfig())} />
|
<meta name="config" content={JSON.stringify(getClientConfig())} />
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="/site.webmanifest"></link>
|
<link rel="manifest" href="/site.webmanifest"></link>
|
||||||
<script src="/serviceWorkerRegister.js" defer></script>
|
<script src="/serviceWorkerRegister.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -56,11 +51,6 @@ export default function RootLayout({
|
|||||||
<GoogleTagManager gtmId={serverConfig.gtmId} />
|
<GoogleTagManager gtmId={serverConfig.gtmId} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{serverConfig?.gaId && (
|
|
||||||
<>
|
|
||||||
<GoogleAnalytics gaId={serverConfig.gaId} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,520 +1,274 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const ar: PartialLocaleType = {
|
const ar: PartialLocaleType = {
|
||||||
WIP: "قريبًا...",
|
WIP: "قريبًا...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "تم اكتشاف مفتاح API غير صالح، يرجى الذهاب إلى [الإعدادات](/#/settings) للتحقق من صحة مفتاح API."
|
"غير مصرح بالوصول، يرجى إدخال رمز الوصول [auth](/#/auth) في صفحة المصادقة.",
|
||||||
: "كلمة المرور غير صحيحة أو فارغة، يرجى الذهاب إلى [تسجيل الدخول](/#/auth) لإدخال كلمة مرور صحيحة، أو أدخل مفتاح OpenAI API الخاص بك في [الإعدادات](/#/settings).",
|
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "تحتاج إلى كلمة مرور",
|
Title: "تحتاج إلى رمز الوصول",
|
||||||
Tips: "قام المشرف بتفعيل التحقق بكلمة المرور، يرجى إدخال رمز الوصول أدناه",
|
Tips: "يرجى إدخال رمز الوصول أدناه",
|
||||||
SubTips: "أو إدخال مفتاح API الخاص بـ OpenAI أو Google",
|
SubTips: "أو أدخل مفتاح واجهة برمجة تطبيقات OpenAI الخاص بك",
|
||||||
Input: "أدخل رمز الوصول هنا",
|
Input: "رمز الوصول",
|
||||||
Confirm: "تأكيد",
|
Confirm: "تأكيد",
|
||||||
Later: "في وقت لاحق",
|
Later: "لاحقًا",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} محادثة`,
|
ChatItemCount: (count: number) => `${count} رسائل`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `إجمالي ${count} محادثة`,
|
SubTitle: (count: number) => ` ${count} رسائل مع ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "تحرير سجل الرسائل",
|
|
||||||
Topic: {
|
|
||||||
Title: "موضوع الدردشة",
|
|
||||||
SubTitle: "تغيير موضوع الدردشة الحالي",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "عرض قائمة الرسائل",
|
ChatList: "الانتقال إلى قائمة الدردشة",
|
||||||
CompressedHistory: "عرض التاريخ المضغوط",
|
CompressedHistory: "ملخص ضغط ذاكرة التاريخ",
|
||||||
Export: "تصدير سجل الدردشة",
|
Export: "تصدير جميع الرسائل كـ Markdown",
|
||||||
Copy: "نسخ",
|
Copy: "نسخ",
|
||||||
Stop: "إيقاف",
|
Stop: "توقف",
|
||||||
Retry: "إعادة المحاولة",
|
Retry: "إعادة المحاولة",
|
||||||
Pin: "تثبيت",
|
|
||||||
PinToastContent: "تم تثبيت 1 محادثة في الإشعارات المسبقة",
|
|
||||||
PinToastAction: "عرض",
|
|
||||||
Delete: "حذف",
|
Delete: "حذف",
|
||||||
Edit: "تحرير",
|
|
||||||
},
|
|
||||||
Commands: {
|
|
||||||
new: "دردشة جديدة",
|
|
||||||
newm: "إنشاء دردشة من القناع",
|
|
||||||
next: "الدردشة التالية",
|
|
||||||
prev: "الدردشة السابقة",
|
|
||||||
clear: "مسح السياق",
|
|
||||||
del: "حذف الدردشة",
|
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
Stop: "إيقاف الاستجابة",
|
Stop: "توقف",
|
||||||
ToBottom: "الانتقال إلى الأحدث",
|
ToBottom: "إلى آخر",
|
||||||
Theme: {
|
Theme: {
|
||||||
auto: "موضوع تلقائي",
|
auto: "تلقائي",
|
||||||
light: "الوضع الفاتح",
|
light: "نمط فاتح",
|
||||||
dark: "الوضع الداكن",
|
dark: "نمط داكن",
|
||||||
},
|
},
|
||||||
Prompt: "الأوامر السريعة",
|
Prompt: "الاقتراحات",
|
||||||
Masks: "جميع الأقنعة",
|
Masks: "الأقنعة",
|
||||||
Clear: "مسح الدردشة",
|
Clear: "مسح السياق",
|
||||||
Settings: "إعدادات الدردشة",
|
Settings: "الإعدادات",
|
||||||
UploadImage: "تحميل صورة",
|
|
||||||
},
|
},
|
||||||
Rename: "إعادة تسمية الدردشة",
|
Rename: "إعادة تسمية الدردشة",
|
||||||
Typing: "يكتب…",
|
Typing: "كتابة...",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} إرسال`;
|
var inputHints = ` اضغط على ${submitKey} للإرسال`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += "، Shift + Enter لإدراج سطر جديد";
|
inputHints += "، Shift + Enter للإنشاء";
|
||||||
}
|
}
|
||||||
return inputHints + "، / لتفعيل الإكمال التلقائي، : لتفعيل الأوامر";
|
return inputHints + "، / للبحث في الاقتراحات";
|
||||||
},
|
},
|
||||||
Send: "إرسال",
|
Send: "إرسال",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "مسح الذاكرة",
|
Reset: "إعادة التعيين إلى الإعدادات الافتراضية",
|
||||||
SaveAs: "حفظ كقناع",
|
SaveAs: "حفظ كأقنعة",
|
||||||
},
|
},
|
||||||
IsContext: "الإشعارات المسبقة",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "مشاركة سجل الدردشة",
|
Title: "تصدير الرسائل",
|
||||||
Copy: "نسخ الكل",
|
Copy: "نسخ الكل",
|
||||||
Download: "تحميل الملف",
|
Download: "تنزيل",
|
||||||
|
MessageFromYou: "رسالة منك",
|
||||||
|
MessageFromChatGPT: "رسالة من ChatGPT",
|
||||||
Share: "مشاركة على ShareGPT",
|
Share: "مشاركة على ShareGPT",
|
||||||
MessageFromYou: "المستخدم",
|
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
Format: {
|
||||||
Title: "تنسيق التصدير",
|
Title: "صيغة التصدير",
|
||||||
SubTitle: "يمكنك تصدير النص كـ Markdown أو صورة PNG",
|
SubTitle: "Markdown أو صورة PNG",
|
||||||
},
|
},
|
||||||
IncludeContext: {
|
IncludeContext: {
|
||||||
Title: "تضمين سياق القناع",
|
Title: "تضمين السياق",
|
||||||
SubTitle: "هل تريد عرض سياق القناع في الرسائل",
|
SubTitle: "تصدير اقتراحات السياق في الأقنعة أم لا",
|
||||||
},
|
},
|
||||||
Steps: {
|
Steps: {
|
||||||
Select: "اختيار",
|
Select: "تحديد",
|
||||||
Preview: "معاينة",
|
Preview: "معاينة",
|
||||||
},
|
},
|
||||||
Image: {
|
|
||||||
Toast: "يتم إنشاء لقطة الشاشة",
|
|
||||||
Modal: "اضغط مطولاً أو انقر بزر الماوس الأيمن لحفظ الصورة",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "بحث في الرسائل",
|
Search: "بحث",
|
||||||
All: "تحديد الكل",
|
All: "تحديد الكل",
|
||||||
Latest: "أحدث الرسائل",
|
Latest: "تحديد أحدث",
|
||||||
Clear: "مسح التحديد",
|
Clear: "مسح",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "ملخص التاريخ",
|
Title: "اقتراحات الذاكرة",
|
||||||
EmptyContent: "محتوى المحادثة قصير جداً، لا حاجة للتلخيص",
|
EmptyContent: "لا شيء حتى الآن.",
|
||||||
Send: "ضغط تلقائي لسجل الدردشة كـ سياق",
|
Send: "إرسال الذاكرة",
|
||||||
Copy: "نسخ الملخص",
|
Copy: "نسخ الذاكرة",
|
||||||
Reset: "[غير مستخدم]",
|
Reset: "إعادة التعيين",
|
||||||
ResetConfirm: "تأكيد مسح ملخص التاريخ؟",
|
ResetConfirm:
|
||||||
|
"سيؤدي إعادة التعيين إلى مسح سجل المحادثة الحالي والذاكرة التاريخية. هل أنت متأكد أنك تريد الاستمرار؟",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "دردشة جديدة",
|
NewChat: "دردشة جديدة",
|
||||||
DeleteChat: "تأكيد حذف المحادثة المحددة؟",
|
DeleteChat: "هل تريد تأكيد حذف المحادثة المحددة؟",
|
||||||
DeleteToast: "تم حذف المحادثة",
|
DeleteToast: "تم حذف الدردشة",
|
||||||
Revert: "تراجع",
|
Revert: "التراجع",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "الإعدادات",
|
Title: "الإعدادات",
|
||||||
SubTitle: "جميع خيارات الإعدادات",
|
SubTitle: "جميع الإعدادات",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "إعادة تعيين جميع الإعدادات",
|
|
||||||
SubTitle: "إعادة تعيين جميع عناصر الإعدادات إلى القيم الافتراضية",
|
|
||||||
Action: "إعادة التعيين الآن",
|
|
||||||
Confirm: "تأكيد إعادة تعيين جميع الإعدادات؟",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "مسح جميع البيانات",
|
|
||||||
SubTitle: "مسح جميع الدردشات وبيانات الإعدادات",
|
|
||||||
Action: "مسح الآن",
|
|
||||||
Confirm: "تأكيد مسح جميع الدردشات وبيانات الإعدادات؟",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // انتبه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة، اتركها كما هي "Language"
|
Name: "Language", // تنبيه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة وتركها "Language"
|
||||||
All: "جميع اللغات",
|
All: "كل اللغات",
|
||||||
},
|
},
|
||||||
Avatar: "الصورة الشخصية",
|
Avatar: "الصورة الرمزية",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "حجم الخط",
|
Title: "حجم الخط",
|
||||||
SubTitle: "حجم الخط في محتوى الدردشة",
|
SubTitle: "ضبط حجم الخط لمحتوى الدردشة",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "خط الدردشة",
|
|
||||||
SubTitle: "خط محتوى الدردشة، اتركه فارغًا لتطبيق الخط الافتراضي العالمي",
|
|
||||||
Placeholder: "اسم الخط",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "حقن الرسائل النصية النظامية",
|
Title: "حقن تلميحات النظام",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"فرض إضافة رسالة نظامية تحاكي ChatGPT في بداية قائمة الرسائل لكل طلب",
|
"قم بإضافة تلميحة نظام محاكاة ChatGPT إلى بداية قائمة الرسائل المُطلَبة في كل طلب",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "معالجة الإدخال من قبل المستخدم",
|
Title: "نموذج الإدخال",
|
||||||
SubTitle: "سيتم ملء آخر رسالة من المستخدم في هذا القالب",
|
SubTitle: "سيتم ملء أحدث رسالة في هذا النموذج",
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `الإصدار الحالي: ${x}`,
|
Version: (x: string) => ` الإصدار: ${x}`,
|
||||||
IsLatest: "أنت على أحدث إصدار",
|
IsLatest: "أحدث إصدار",
|
||||||
CheckUpdate: "التحقق من التحديثات",
|
CheckUpdate: "التحقق من التحديث",
|
||||||
IsChecking: "جارٍ التحقق من التحديثات...",
|
IsChecking: "جارٍ التحقق من التحديث...",
|
||||||
FoundUpdate: (x: string) => `تم العثور على إصدار جديد: ${x}`,
|
FoundUpdate: (x: string) => ` تم العثور على إصدار جديد: ${x}`,
|
||||||
GoToUpdate: "انتقل للتحديث",
|
GoToUpdate: "التحديث",
|
||||||
},
|
},
|
||||||
SendKey: "زر الإرسال",
|
SendKey: "مفتاح الإرسال",
|
||||||
Theme: "السمة",
|
Theme: "السمة",
|
||||||
TightBorder: "وضع بدون حدود",
|
TightBorder: "حدود ضيقة",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "فقاعة المعاينة",
|
Title: "عرض معاينة الـ Send",
|
||||||
SubTitle: "معاينة محتوى Markdown في فقاعة المعاينة",
|
SubTitle: "معاينة Markdown في فقاعة",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "توليد العنوان تلقائيًا",
|
|
||||||
SubTitle: "توليد عنوان مناسب بناءً على محتوى الدردشة",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "بيانات السحابة",
|
|
||||||
NotSyncYet: "لم يتم التزامن بعد",
|
|
||||||
Success: "تم التزامن بنجاح",
|
|
||||||
Fail: "فشل التزامن",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "تكوين التزامن السحابي",
|
|
||||||
Check: "التحقق من التوفر",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "نوع التزامن",
|
|
||||||
SubTitle: "اختر خادم التزامن المفضل",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "تفعيل الوكيل",
|
|
||||||
SubTitle: "يجب تفعيل الوكيل عند التزامن عبر المتصفح لتجنب قيود CORS",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "عنوان الوكيل",
|
|
||||||
SubTitle: "ينطبق فقط على الوكيل المتاح في هذا المشروع",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "عنوان WebDAV",
|
|
||||||
UserName: "اسم المستخدم",
|
|
||||||
Password: "كلمة المرور",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "رابط UpStash Redis REST",
|
|
||||||
UserName: "اسم النسخ الاحتياطي",
|
|
||||||
Password: "رمز UpStash Redis REST",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "بيانات محلية",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} دردشة، ${overview.message} رسالة، ${overview.prompt} إشعار، ${overview.mask} قناع`;
|
|
||||||
},
|
|
||||||
ImportFailed: "فشل الاستيراد",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "صفحة بدء القناع",
|
Title: "شاشة تظهر الأقنعة",
|
||||||
SubTitle: "عرض صفحة بدء القناع عند بدء دردشة جديدة",
|
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة",
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "إخفاء الأقنعة المدمجة",
|
|
||||||
SubTitle: "إخفاء الأقنعة المدمجة في قائمة الأقنعة",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "تعطيل الإكمال التلقائي للإشعارات",
|
Title: "تعطيل الاكتمال التلقائي",
|
||||||
SubTitle: "استخدم / في بداية مربع النص لتفعيل الإكمال التلقائي",
|
SubTitle: "اكتب / لتشغيل الاكتمال التلقائي",
|
||||||
},
|
},
|
||||||
List: "قائمة الإشعارات المخصصة",
|
List: "قائمة الاقتراحات",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) => `
|
||||||
`مدمج ${builtin} إشعار، مخصص ${custom} إشعار`,
|
${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخدم`,
|
||||||
Edit: "تحرير",
|
Edit: "تعديل",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "قائمة الإشعارات",
|
Title: "قائمة الاقتراحات",
|
||||||
Add: "جديد",
|
Add: "إضافة واحدة",
|
||||||
Search: "بحث عن إشعارات",
|
Search: "البحث في الاقتراحات",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "تحرير الإشعارات",
|
Title: "تحرير الاقتراح",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "عدد الرسائل التاريخية المرفقة",
|
Title: "عدد الرسائل المرفقة",
|
||||||
SubTitle: "عدد الرسائل التاريخية المرفقة مع كل طلب",
|
SubTitle: "عدد الرسائل المرسلة المرفقة في كل طلب",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "عتبة ضغط طول الرسائل التاريخية",
|
Title: "حد الضغط للتاريخ",
|
||||||
SubTitle:
|
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
|
||||||
"عندما يتجاوز طول الرسائل التاريخية غير المضغوطة هذه القيمة، سيتم الضغط",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "التحقق من الرصيد",
|
Title: "رصيد الحساب",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `تم استخدام $${used} هذا الشهر، إجمالي الاشتراك $${total}`;
|
return `تم استخدام $${used} من هذا الشهر، الاشتراك ${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "جارٍ التحقق...",
|
IsChecking: "جارٍ التحقق...",
|
||||||
Check: "إعادة التحقق",
|
Check: "التحقق",
|
||||||
NoAccess: "أدخل مفتاح API أو كلمة مرور للوصول إلى الرصيد",
|
NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
|
||||||
},
|
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "كلمة المرور للوصول",
|
|
||||||
SubTitle: "قام المشرف بتمكين الوصول المشفر",
|
|
||||||
Placeholder: "أدخل كلمة المرور للوصول",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "واجهة مخصصة",
|
|
||||||
SubTitle: "هل تستخدم خدمة Azure أو OpenAI مخصصة",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "موفر الخدمة النموذجية",
|
|
||||||
SubTitle: "التبديل بين مقدمي الخدمة المختلفين",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح API",
|
|
||||||
SubTitle: "استخدم مفتاح OpenAI مخصص لتجاوز قيود كلمة المرور",
|
|
||||||
Placeholder: "مفتاح OpenAI API",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "يجب أن يحتوي على http(s):// بخلاف العنوان الافتراضي",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح الواجهة",
|
|
||||||
SubTitle: "استخدم مفتاح Azure مخصص لتجاوز قيود كلمة المرور",
|
|
||||||
Placeholder: "مفتاح Azure API",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "مثال:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "إصدار الواجهة (azure api version)",
|
|
||||||
SubTitle: "اختر إصدارًا معينًا",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح الواجهة",
|
|
||||||
SubTitle: "استخدم مفتاح Anthropic مخصص لتجاوز قيود كلمة المرور",
|
|
||||||
Placeholder: "مفتاح Anthropic API",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "مثال:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "إصدار الواجهة (claude api version)",
|
|
||||||
SubTitle: "اختر إصدار API محدد",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح API",
|
|
||||||
SubTitle: "احصل على مفتاح API الخاص بك من Google AI",
|
|
||||||
Placeholder: "أدخل مفتاح Google AI Studio API",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان النهاية",
|
|
||||||
SubTitle: "مثال:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "إصدار API (مخصص لـ gemini-pro)",
|
|
||||||
SubTitle: "اختر إصدار API معين",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "مستوى تصفية الأمان من Google",
|
|
||||||
SubTitle: "تعيين مستوى تصفية المحتوى",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح API",
|
|
||||||
SubTitle: "استخدم مفتاح Baidu API مخصص",
|
|
||||||
Placeholder: "مفتاح Baidu API",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "المفتاح السري",
|
|
||||||
SubTitle: "استخدم مفتاح Baidu Secret مخصص",
|
|
||||||
Placeholder: "مفتاح Baidu Secret",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "لا يدعم التخصيص، انتقل إلى .env للتكوين",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح الواجهة",
|
|
||||||
SubTitle: "استخدم مفتاح ByteDance API مخصص",
|
|
||||||
Placeholder: "مفتاح ByteDance API",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "مثال:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "مفتاح الواجهة",
|
|
||||||
SubTitle: "استخدم مفتاح Alibaba Cloud API مخصص",
|
|
||||||
Placeholder: "مفتاح Alibaba Cloud API",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "عنوان الواجهة",
|
|
||||||
SubTitle: "مثال:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "اسم النموذج المخصص",
|
|
||||||
SubTitle: "أضف خيارات نموذج مخصص، مفصولة بفواصل إنجليزية",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "النموذج",
|
Model: "النموذج",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "العشوائية (temperature)",
|
Title: "الحرارة",
|
||||||
SubTitle: "كلما زادت القيمة، زادت العشوائية في الردود",
|
SubTitle: "قيمة أكبر تجعل الإخراج أكثر عشوائية",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "عينات النواة (top_p)",
|
|
||||||
SubTitle: "مشابه للعشوائية ولكن لا تغيره مع العشوائية",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "حد أقصى للرموز لكل رد (max_tokens)",
|
Title: "الحد الأقصى للرموز",
|
||||||
SubTitle: "أقصى عدد للرموز في تفاعل واحد",
|
SubTitle: "الحد الأقصى لعدد الرموز المدخلة والرموز المُنشأة",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "تجدد الموضوع (presence_penalty)",
|
Title: "تأثير الوجود",
|
||||||
SubTitle: "كلما زادت القيمة، زادت احتمالية التوسع في مواضيع جديدة",
|
SubTitle: "قيمة أكبر تزيد من احتمالية التحدث عن مواضيع جديدة",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "عقوبة التكرار (frequency_penalty)",
|
Title: "تأثير التكرار",
|
||||||
SubTitle: "كلما زادت القيمة، زادت احتمالية تقليل تكرار الكلمات",
|
SubTitle: "قيمة أكبر تقلل من احتمالية تكرار نفس السطر",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "دردشة جديدة",
|
DefaultTopic: "محادثة جديدة",
|
||||||
BotHello: "كيف يمكنني مساعدتك؟",
|
BotHello: "مرحبًا! كيف يمكنني مساعدتك اليوم؟",
|
||||||
Error: "حدث خطأ، يرجى المحاولة مرة أخرى لاحقًا",
|
Error: "حدث خطأ ما، يرجى المحاولة مرة أخرى في وقت لاحق.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) => "هذا ملخص لسجل الدردشة كمراجعة: " + content,
|
||||||
"هذا ملخص للدردشة السابقة كنقطة انطلاق: " + content,
|
|
||||||
Topic:
|
Topic:
|
||||||
"استخدم أربع إلى خمس كلمات لإرجاع ملخص مختصر لهذه الجملة، بدون شرح، بدون علامات ترقيم، بدون كلمات تعبيرية، بدون نص إضافي، بدون تنسيق عريض، إذا لم يكن هناك موضوع، يرجى العودة إلى 'دردشة عامة'",
|
"يرجى إنشاء عنوان يتكون من أربع إلى خمس كلمات يلخص محادثتنا دون أي مقدمة أو ترقيم أو علامات ترقيم أو نقاط أو رموز إضافية. قم بإزالة علامات التنصيص المحيطة.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"قم بتلخيص محتوى الدردشة باختصار، لاستخدامه كإشارة سياقية لاحقة، اجعلها في حدود 200 كلمة",
|
"قم بتلخيص النقاش بشكل موجز في 200 كلمة أو أقل لاستخدامه كاقتراح للسياق في المستقبل.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "تم الكتابة إلى الحافظة",
|
Success: "تم النسخ إلى الحافظة",
|
||||||
Failed: "فشل النسخ، يرجى منح أذونات الحافظة",
|
Failed: "فشلت عملية النسخ، يرجى منح الإذن للوصول إلى الحافظة",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "تم تنزيل المحتوى إلى مجلدك.",
|
|
||||||
Failed: "فشل التنزيل.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `يحتوي على ${x} إشعارات مخصصة`,
|
Toast: (x: any) => `مع ${x} اقتراحًا ذا سياق`,
|
||||||
Edit: "إعدادات الدردشة الحالية",
|
Edit: "الاقتراحات السياقية والذاكرة",
|
||||||
Add: "إضافة دردشة جديدة",
|
Add: "إضافة اقتراح",
|
||||||
Clear: "تم مسح السياق",
|
Clear: "مسح السياق",
|
||||||
Revert: "استعادة السياق",
|
Revert: "التراجع",
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "الإضافات",
|
Name: "المكوّن الإضافي",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "أنت مساعد",
|
Sysmessage: "أنت مساعد ي",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "القناع",
|
Name: "الأقنعة",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "أقنعة الأدوار المخصصة",
|
Title: "قالب الاقتراح",
|
||||||
SubTitle: (count: number) => `${count} تعريف لدور مخصص`,
|
SubTitle: (count: number) => `${count} قوالب الاقتراح`,
|
||||||
Search: "بحث عن قناع الدور",
|
Search: "البحث في القوالب",
|
||||||
Create: "إنشاء جديد",
|
Create: "إنشاء",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `يحتوي على ${count} محادثات مخصصة`,
|
Info: (count: number) => `${count} اقتراحات`,
|
||||||
Chat: "الدردشة",
|
Chat: "الدردشة",
|
||||||
View: "عرض",
|
View: "عرض",
|
||||||
Edit: "تحرير",
|
Edit: "تعديل",
|
||||||
Delete: "حذف",
|
Delete: "حذف",
|
||||||
DeleteConfirm: "تأكيد الحذف؟",
|
DeleteConfirm: "تأكيد الحذف؟",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) => `
|
||||||
`تحرير القناع المخصص ${readonly ? " (للقراءة فقط)" : ""}`,
|
تعديل قالب الاقتراح ${readonly ? "(للقراءة فقط)" : ""}`,
|
||||||
Download: "تنزيل القناع المخصص",
|
Download: "تنزيل",
|
||||||
Clone: "استنساخ القناع",
|
Clone: "استنساخ",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "صورة الدور",
|
Avatar: "صورة الروبوت",
|
||||||
Name: "اسم الدور",
|
Name: "اسم الروبوت",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "استخدام الإعدادات العالمية",
|
Title: "استخدام الإعدادات العامة",
|
||||||
SubTitle: "هل تستخدم الدردشة الحالية الإعدادات العالمية للنموذج",
|
SubTitle: "استخدام الإعدادات العامة في هذه الدردشة",
|
||||||
Confirm:
|
Confirm: "تأكيد الاستبدال بالإعدادات المخصصة بالإعدادات العامة؟",
|
||||||
"ستتم الكتابة فوق الإعدادات المخصصة للدردشة الحالية تلقائيًا، تأكيد تفعيل الإعدادات العالمية؟",
|
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "إخفاء المحادثات المخصصة",
|
Title: "إخفاء اقتراحات السياق",
|
||||||
SubTitle: "بعد الإخفاء، لن تظهر المحادثات المخصصة في واجهة الدردشة",
|
SubTitle: "عدم عرض اقتراحات السياق في الدردشة",
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "مشاركة هذا القناع",
|
|
||||||
SubTitle: "إنشاء رابط مباشر لهذا القناع",
|
|
||||||
Action: "نسخ الرابط",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "العودة",
|
Return: "العودة",
|
||||||
Skip: "بدء الآن",
|
Skip: "ابدأ فقط",
|
||||||
|
Title: "اختيار قناع",
|
||||||
|
SubTitle: "دردشة مع الروح وراء القناع",
|
||||||
|
More: "المزيد",
|
||||||
NotShow: "عدم العرض مرة أخرى",
|
NotShow: "عدم العرض مرة أخرى",
|
||||||
ConfirmNoShow:
|
ConfirmNoShow: "تأكيد تعطيله؟ يمكنك تمكينه في الإعدادات لاحقًا.",
|
||||||
"تأكيد إلغاء العرض؟ بعد الإلغاء، يمكنك إعادة تفعيله في الإعدادات في أي وقت.",
|
|
||||||
Title: "اختر قناعًا",
|
|
||||||
SubTitle: "ابدأ الآن وتفاعل مع الأفكار خلف القناع",
|
|
||||||
More: "عرض الكل",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "تم الكشف عن رمز وصول في الرابط، هل تريد تعبئته تلقائيًا؟",
|
|
||||||
Settings: "تم الكشف عن إعدادات مسبقة في الرابط، هل تريد تعبئتها تلقائيًا؟",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
@@ -522,16 +276,9 @@ const ar: PartialLocaleType = {
|
|||||||
Cancel: "إلغاء",
|
Cancel: "إلغاء",
|
||||||
Close: "إغلاق",
|
Close: "إغلاق",
|
||||||
Create: "إنشاء",
|
Create: "إنشاء",
|
||||||
Edit: "تحرير",
|
Edit: "تعديل",
|
||||||
Export: "تصدير",
|
|
||||||
Import: "استيراد",
|
|
||||||
Sync: "مزامنة",
|
|
||||||
Config: "تكوين",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "فقط الرسائل بعد مسح السياق سيتم عرضها",
|
|
||||||
},
|
|
||||||
Model: "النموذج",
|
Model: "النموذج",
|
||||||
Messages: "الرسائل",
|
Messages: "الرسائل",
|
||||||
Topic: "الموضوع",
|
Topic: "الموضوع",
|
||||||
|
|||||||
@@ -1,549 +1,332 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const bn: PartialLocaleType = {
|
const bn: PartialLocaleType = {
|
||||||
WIP: "শীঘ্রই আসছে...",
|
WIP: "শীঘ্রই আসছে...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "অবৈধ API কী সনাক্ত করা হয়েছে, অনুগ্রহ করে [সেটিংস](/#/settings) পৃষ্ঠায় যান এবং নিশ্চিত করুন যে API কী সঠিকভাবে কনফিগার করা হয়েছে।"
|
"অননুমোদিত অ্যাক্সেস, অনুগ্রহ করে [অথোরাইজশন](/#/auth) পৃষ্ঠায় অ্যাক্সেস কোড ইনপুট করুন।",
|
||||||
: "অ্যাক্সেস পাসওয়ার্ড সঠিক নয় বা খালি, অনুগ্রহ করে [লগইন](/#/auth) পৃষ্ঠায় যান এবং সঠিক অ্যাক্সেস পাসওয়ার্ড প্রবেশ করান, অথবা [সেটিংস](/#/settings) পৃষ্ঠায় আপনার OpenAI API কী প্রবেশ করান।",
|
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "পাসওয়ার্ড প্রয়োজন",
|
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
||||||
Tips: "অ্যাডমিন পাসওয়ার্ড প্রমাণীকরণ চালু করেছেন, নিচে অ্যাক্সেস কোড প্রবেশ করুন",
|
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
||||||
SubTips: "অথবা আপনার OpenAI অথবা Google API কী প্রবেশ করান",
|
SubTips: "অথবা আপনার OpenAI API কী প্রবেশ করুন",
|
||||||
Input: "এখানে অ্যাক্সেস কোড লিখুন",
|
Input: "অ্যাক্সেস কোড",
|
||||||
Confirm: "নিশ্চিত করুন",
|
Confirm: "নিশ্চিত করুন",
|
||||||
Later: "পরে বলুন",
|
Later: "পরে",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} টি চ্যাট`,
|
ChatItemCount: (count: number) => `${count} টি বার্তা`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `মোট ${count} টি চ্যাট`,
|
SubTitle: (count: number) => `${count} টি বার্তা`,
|
||||||
EditMessage: {
|
|
||||||
Title: "বার্তাগুলি সম্পাদনা করুন",
|
|
||||||
Topic: {
|
|
||||||
Title: "চ্যাটের বিষয়",
|
|
||||||
SubTitle: "বর্তমান চ্যাটের বিষয় পরিবর্তন করুন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "বার্তা তালিকা দেখুন",
|
ChatList: "চ্যাট তালিকায় যান",
|
||||||
CompressedHistory: "সংকুচিত ইতিহাস দেখুন",
|
CompressedHistory: "সংক্ষিপ্ত ইতিহাস মেমোরি প্রম্পট",
|
||||||
Export: "চ্যাট ইতিহাস রপ্তানী করুন",
|
Export: "সমস্ত বার্তা মার্কডাউন হিসাবে রপ্তানি করুন",
|
||||||
Copy: "অনুলিপি করুন",
|
Copy: "কপি",
|
||||||
Stop: "থামান",
|
Stop: "বন্ধ করুন",
|
||||||
Retry: "পুনরায় চেষ্টা করুন",
|
Retry: "পুনরায় চেষ্টা করুন",
|
||||||
Pin: "পিন করুন",
|
Pin: "পিন করুন",
|
||||||
PinToastContent: "1 টি চ্যাট পূর্বনির্ধারিত প্রম্পটে পিন করা হয়েছে",
|
PinToastContent: "পিন করা হয়েছে ২টি বার্তা প্রম্পটে",
|
||||||
PinToastAction: "দেখুন",
|
PinToastAction: "দেখুন",
|
||||||
Delete: "মুছে ফেলুন",
|
Delete: "মুছে ফেলুন",
|
||||||
Edit: "সম্পাদনা করুন",
|
Edit: "সম্পাদন করুন",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "নতুন চ্যাট",
|
new: "নতুন চ্যাট শুরু করুন",
|
||||||
newm: "মাস্ক থেকে নতুন চ্যাট",
|
newm: "মাস্ক সহ নতুন চ্যাট শুরু করুন",
|
||||||
next: "পরবর্তী চ্যাট",
|
next: "পরবর্তী চ্যাট",
|
||||||
prev: "পূর্ববর্তী চ্যাট",
|
prev: "পূর্ববর্তী চ্যাট",
|
||||||
clear: "প্রসঙ্গ পরিষ্কার করুন",
|
clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
|
||||||
del: "চ্যাট মুছে ফেলুন",
|
del: "চ্যাট মুছুন",
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
Stop: "প্রতিক্রিয়া থামান",
|
Stop: "বন্ধ করুন",
|
||||||
ToBottom: "সর্বশেষে স্ক্রোল করুন",
|
ToBottom: "সর্বশেষতম দিকে",
|
||||||
Theme: {
|
Theme: {
|
||||||
auto: "স্বয়ংক্রিয় থিম",
|
auto: "অটো",
|
||||||
light: "আলোর মোড",
|
light: "হালকা থিম",
|
||||||
dark: "অন্ধকার মোড",
|
dark: "ডার্ক থিম",
|
||||||
},
|
},
|
||||||
Prompt: "সংক্ষিপ্ত নির্দেশনা",
|
Prompt: "প্রম্পটগুলিতে",
|
||||||
Masks: "সমস্ত মাস্ক",
|
Masks: "মাস্কগুলি",
|
||||||
Clear: "চ্যাট পরিষ্কার করুন",
|
Clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
|
||||||
Settings: "চ্যাট সেটিংস",
|
Settings: "সেটিংস",
|
||||||
UploadImage: "চিত্র আপলোড করুন",
|
|
||||||
},
|
},
|
||||||
Rename: "চ্যাট নাম পরিবর্তন করুন",
|
Rename: "চ্যাট পুনঃনামকরণ করুন",
|
||||||
Typing: "লিখছে…",
|
Typing: "টাইপিং...",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} পাঠান`;
|
var inputHints = `${submitKey} to send`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter নতুন লাইন";
|
inputHints += ", Shift + Enter to wrap";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ পূর্ণতা সক্রিয় করুন,: কমান্ড সক্রিয় করুন";
|
return inputHints + ", / to search prompts, : to use commands";
|
||||||
},
|
},
|
||||||
Send: "পাঠান",
|
Send: "প্রেরণ করুন",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "মেমরি মুছে ফেলুন",
|
Reset: "ডিফল্টে রিসেট করুন",
|
||||||
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
|
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
|
||||||
},
|
},
|
||||||
IsContext: "পূর্বনির্ধারিত প্রম্পট",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "চ্যাট ইতিহাস শেয়ার করুন",
|
Title: "বার্তা রপ্তানিকরণ",
|
||||||
Copy: "সবকিছু কপি করুন",
|
Copy: "সমস্তটি কপি করুন",
|
||||||
Download: "ফাইল ডাউনলোড করুন",
|
Download: "ডাউনলোড করুন",
|
||||||
Share: "ShareGPT তে শেয়ার করুন",
|
MessageFromYou: "আপনার বার্তা",
|
||||||
MessageFromYou: "ব্যবহারকারী",
|
MessageFromChatGPT: "চ্যাটজিপিটির বার্তা",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
Share: "শেয়ার করুন শেয়ারজিপিটি তে",
|
||||||
Format: {
|
Format: {
|
||||||
Title: "রপ্তানির ফর্ম্যাট",
|
Title: "রপ্তানি ফরম্যাট",
|
||||||
SubTitle: "Markdown টেক্সট বা PNG চিত্র রপ্তানি করা যাবে",
|
SubTitle: "মার্কডাউন বা পিএনজি চিত্র",
|
||||||
},
|
},
|
||||||
IncludeContext: {
|
IncludeContext: {
|
||||||
Title: "মাস্ক প্রসঙ্গ অন্তর্ভুক্ত করুন",
|
Title: "মাস্ক অন্তর্ভুক্ত করুন",
|
||||||
SubTitle: "বার্তায় মাস্ক প্রসঙ্গ প্রদর্শন করা হবে কি না",
|
SubTitle: "মাস্কগুলি সংরক্ষণ করবেন না কি",
|
||||||
},
|
},
|
||||||
Steps: {
|
Steps: {
|
||||||
Select: "নির্বাচন করুন",
|
Select: "নির্বাচন করুন",
|
||||||
Preview: "পূর্বরূপ দেখুন",
|
Preview: "প্রিভিউ করুন",
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "স্ক্রীনশট তৈরি করা হচ্ছে",
|
|
||||||
Modal: "ছবি সংরক্ষণ করতে দীর্ঘ প্রেস করুন অথবা রাইট ক্লিক করুন",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "বার্তা অনুসন্ধান করুন",
|
Search: "অনুসন্ধান করুন",
|
||||||
All: "সবকিছু নির্বাচন করুন",
|
All: "সমস্তটি নির্বাচন করুন",
|
||||||
Latest: "সর্বশেষ কিছু",
|
Latest: "সর্বশেষতমটি নির্বাচন করুন",
|
||||||
Clear: "নির্বাচন পরিষ্কার করুন",
|
Clear: "পরিষ্কার করুন",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "ইতিহাস সারাংশ",
|
Title: "মেমোরি প্রম্পট",
|
||||||
EmptyContent: "চ্যাটের বিষয়বস্তু খুব সংক্ষিপ্ত, সারাংশ প্রয়োজন নেই",
|
EmptyContent: "এখনও কিছুই নেই।",
|
||||||
Send: "অটোমেটিক চ্যাট ইতিহাস সংকুচিত করুন এবং প্রসঙ্গ হিসেবে পাঠান",
|
Send: "মেমোরি প্রেরণ করুন",
|
||||||
Copy: "সারাংশ কপি করুন",
|
Copy: "মেমোরি কপি করুন",
|
||||||
Reset: "[unused]",
|
Reset: "পুনরায় নিশ্চিত করুন",
|
||||||
ResetConfirm: "ইতিহাস সারাংশ মুছে ফেলার নিশ্চিত করুন?",
|
ResetConfirm:
|
||||||
|
"রিসেট করলে বর্তমান চ্যাট ইতিহাস এবং ঐতিহাসিক মেমোরি মুছে যাবে। পুনরায় নির্দিষ্ট করতে চান তা নিশ্চিত করতে চান?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "নতুন চ্যাট",
|
NewChat: "নতুন চ্যাট",
|
||||||
DeleteChat: "নির্বাচিত চ্যাট মুছে ফেলার নিশ্চিত করুন?",
|
DeleteChat: "নির্বাচিত সংলাপটি মুছতে নিশ্চিত করুন?",
|
||||||
DeleteToast: "চ্যাট মুছে ফেলা হয়েছে",
|
DeleteToast: "চ্যাটটি মুছেছেন",
|
||||||
Revert: "পূর্বাবস্থায় ফেরান",
|
Revert: "পুনরায়",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "সেটিংস",
|
Title: "সেটিংস",
|
||||||
SubTitle: "সমস্ত সেটিংস অপশন",
|
SubTitle: "সমস্ত সেটিংস",
|
||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "সমস্ত সেটিংস পুনরায় সেট করুন",
|
Title: "সমস্ত সেটিংস পুনঃনির্দেশ দিন",
|
||||||
SubTitle: "সমস্ত সেটিংস বিকল্পগুলিকে ডিফল্ট মানে পুনরায় সেট করুন",
|
SubTitle: "সকল সেটিংস ডিফল্টে পুনঃনির্দেশ দিতে",
|
||||||
Action: "এখনই পুনরায় সেট করুন",
|
Action: "পুনঃনির্দেশ দিন",
|
||||||
Confirm: "সমস্ত সেটিংস পুনরায় সেট করার নিশ্চিত করুন?",
|
Confirm: "সমস্ত সেটিংস ডিফল্টে পুনঃনির্দেশ করতে নিশ্চিত করতে?",
|
||||||
},
|
},
|
||||||
Clear: {
|
Clear: {
|
||||||
Title: "সমস্ত তথ্য মুছে ফেলুন",
|
Title: "সমস্ত তথ্য মুছুন",
|
||||||
SubTitle: "সমস্ত চ্যাট এবং সেটিংস ডেটা মুছে ফেলুন",
|
SubTitle: "সমস্ত বার্তা এবং সেটিংস মুছুন",
|
||||||
Action: "এখনই মুছে ফেলুন",
|
Action: "মুছুন",
|
||||||
Confirm: "সমস্ত চ্যাট এবং সেটিংস ডেটা মুছে ফেলানোর নিশ্চিত করুন?",
|
Confirm: "সমস্ত বার্তা এবং সেটিংস মুছে ফেলতে নিশ্চিত করতে?",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "বাংলা", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "সমস্ত ভাষা",
|
All: "সমস্ত ভাষা",
|
||||||
},
|
},
|
||||||
Avatar: "অভিনেতা",
|
Avatar: "অবতার",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "ফন্ট সাইজ",
|
Title: "ফন্ট সাইজ",
|
||||||
SubTitle: "চ্যাট কনটেন্টের ফন্ট সাইজ",
|
SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "চ্যাট ফন্ট",
|
|
||||||
SubTitle:
|
|
||||||
"চ্যাট সামগ্রীর ফন্ট, বিশ্বব্যাপী ডিফল্ট ফন্ট প্রয়োগ করতে খালি রাখুন",
|
|
||||||
Placeholder: "ফন্টের নাম",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "সিস্টেম-লেভেল প্রম্পট যোগ করুন",
|
Title: "حقن تلميحات النظام",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"প্রত্যেক বার্তায় একটি সিস্টেম প্রম্পট যোগ করুন যা ChatGPT এর অনুকরণ করবে",
|
"قم بإضافة تلميحة نظام محاكاة ChatGPT إلى بداية قائمة الرسائل المُطلَبة في كل طلب",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "ব্যবহারকারীর ইনপুট প্রিপ্রসেসিং",
|
Title: "ইনপুট টেমপ্লেট",
|
||||||
SubTitle: "ব্যবহারকারীর সর্বশেষ বার্তা এই টেমপ্লেটে পূরণ করা হবে",
|
SubTitle: "নতুনতম বার্তা এই টেমপ্লেটে পূরণ হবে",
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `বর্তমান সংস্করণ: ${x}`,
|
Version: (x: string) => `Version: ${x}`,
|
||||||
IsLatest: "এটি সর্বশেষ সংস্করণ",
|
IsLatest: "Latest version",
|
||||||
CheckUpdate: "আপডেট পরীক্ষা করুন",
|
CheckUpdate: "Check Update",
|
||||||
IsChecking: "আপডেট পরীক্ষা করা হচ্ছে...",
|
IsChecking: "Checking update...",
|
||||||
FoundUpdate: (x: string) => `নতুন সংস্করণ পাওয়া গিয়েছে: ${x}`,
|
FoundUpdate: (x: string) => `Found new version: ${x}`,
|
||||||
GoToUpdate: "আপডেট করতে যান",
|
GoToUpdate: "Update",
|
||||||
},
|
},
|
||||||
SendKey: "পাঠানোর কী",
|
SendKey: "প্রেরণ চাবি",
|
||||||
Theme: "থিম",
|
Theme: "থিম",
|
||||||
TightBorder: "বর্ডার-বিহীন মোড",
|
TightBorder: "সঙ্গতি সীমা",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "প্রিভিউ বুদবুদ",
|
Title: "প্রিভিউ বুলবুল প্রেরণ করুন",
|
||||||
SubTitle: "প্রিভিউ বুদবুদে Markdown কনটেন্ট প্রিভিউ করুন",
|
SubTitle: "বুলবুলে মার্কডাউন প্রিভিউ করুন",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "স্বয়ংক্রিয় শিরোনাম জেনারেশন",
|
|
||||||
SubTitle: "চ্যাট কনটেন্টের ভিত্তিতে উপযুক্ত শিরোনাম তৈরি করুন",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "ক্লাউড ডেটা",
|
|
||||||
NotSyncYet: "এখনো সিঙ্ক করা হয়নি",
|
|
||||||
Success: "সিঙ্ক সফল",
|
|
||||||
Fail: "সিঙ্ক ব্যর্থ",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "ক্লাউড সিঙ্ক কনফিগার করুন",
|
|
||||||
Check: "পরীক্ষা করুন",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "সিঙ্ক টাইপ",
|
|
||||||
SubTitle: "পছন্দসই সিঙ্ক সার্ভার নির্বাচন করুন",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "প্রক্সি সক্রিয় করুন",
|
|
||||||
SubTitle:
|
|
||||||
"ব্রাউজারে সিঙ্ক করার সময়, ক্রস-অরিজিন সীমাবদ্ধতা এড়াতে প্রক্সি সক্রিয় করতে হবে",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "প্রক্সি ঠিকানা",
|
|
||||||
SubTitle:
|
|
||||||
"এটি শুধুমাত্র প্রকল্পের সাথে সরবরাহিত ক্রস-অরিজিন প্রক্সির জন্য প্রযোজ্য",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV ঠিকানা",
|
|
||||||
UserName: "ব্যবহারকারীর নাম",
|
|
||||||
Password: "পাসওয়ার্ড",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST URL",
|
|
||||||
UserName: "ব্যাকআপ নাম",
|
|
||||||
Password: "UpStash Redis REST টোকেন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "স্থানীয় ডেটা",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} বার চ্যাট, ${overview.message} বার্তা, ${overview.prompt} প্রম্পট, ${overview.mask} মাস্ক`;
|
|
||||||
},
|
|
||||||
ImportFailed: "আমদানি ব্যর্থ",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "মাস্ক লঞ্চ পেজ",
|
Title: "মাস্ক স্প্ল্যাশ স্ক্রিন",
|
||||||
SubTitle: "নতুন চ্যাট শুরু করার সময় মাস্ক লঞ্চ পেজ প্রদর্শন করুন",
|
SubTitle:
|
||||||
|
"নতুন চ্যাট শুরু করার আগে মাস্ক স্প্ল্যাশ স্ক্রিন প্রদর্শন করুন",
|
||||||
},
|
},
|
||||||
Builtin: {
|
Builtin: {
|
||||||
Title: "ইনবিল্ট মাস্ক লুকান",
|
Title: "মূলত মাস্ক গোপন করুন",
|
||||||
SubTitle: "সমস্ত মাস্ক তালিকায় ইনবিল্ট মাস্ক লুকান",
|
SubTitle: "মাস্ক তালিকা থেকে মূলত মাস্কগুলি লুকান",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "প্রম্পট অটো-কমপ্লিশন নিষ্ক্রিয় করুন",
|
Title: "অটো-সম্পূর্ণতা নিষ্ক্রিয় করুন",
|
||||||
SubTitle: "ইনপুট বক্সের শুরুতে / টাইপ করলে অটো-কমপ্লিশন সক্রিয় হবে",
|
SubTitle: "অটো-সম্পূর্ণতা চালু করতে / ইনপুট করুন",
|
||||||
},
|
},
|
||||||
List: "স্বনির্ধারিত প্রম্পট তালিকা",
|
List: "প্রম্পট তালিকা",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`ইনবিল্ট ${builtin} টি, ব্যবহারকারী সংজ্ঞায়িত ${custom} টি`,
|
`${builtin} built-in, ${custom} user-defined`,
|
||||||
Edit: "সম্পাদনা করুন",
|
Edit: "সম্পাদন করুন",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "প্রম্পট তালিকা",
|
Title: "প্রম্পট তালিকা",
|
||||||
Add: "নতুন করুন",
|
Add: "একটি যোগ করুন",
|
||||||
Search: "প্রম্পট অনুসন্ধান করুন",
|
Search: "সন্ধান প্রম্পট",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "প্রম্পট সম্পাদনা করুন",
|
Title: "সম্পাদন করুন প্রম্পট",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "সংযুক্ত ইতিহাস বার্তার সংখ্যা",
|
Title: "সংযুক্ত বার্তা সংখ্যা",
|
||||||
SubTitle: "প্রতিটি অনুরোধে সংযুক্ত ইতিহাস বার্তার সংখ্যা",
|
SubTitle: "প্রতি অনুরোধে প্রেরণ করা গেলে প্রেরণ করা হবে",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "ইতিহাস বার্তা দৈর্ঘ্য সংকুচিত থ্রেশহোল্ড",
|
Title: "ইতিহাস সঙ্কুচিত করার সীমা",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"যখন সংকুচিত ইতিহাস বার্তা এই মান ছাড়িয়ে যায়, তখন সংকুচিত করা হবে",
|
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "ব্যালেন্স চেক",
|
Title: "একাউন্ট ব্যালেন্স",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন মোট $${total}`;
|
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "পরীক্ষা করা হচ্ছে…",
|
IsChecking: "চেক করা হচ্ছে...",
|
||||||
Check: "পুনরায় পরীক্ষা করুন",
|
Check: "চেক",
|
||||||
NoAccess: "ব্যালেন্স দেখতে API কী অথবা অ্যাক্সেস পাসওয়ার্ড প্রবেশ করুন",
|
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "মডেল",
|
||||||
AccessCode: {
|
|
||||||
Title: "অ্যাক্সেস পাসওয়ার্ড",
|
|
||||||
SubTitle: "অ্যাডমিন এনক্রিপ্টেড অ্যাক্সেস সক্রিয় করেছেন",
|
|
||||||
Placeholder: "অ্যাক্সেস পাসওয়ার্ড প্রবেশ করুন",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "স্বনির্ধারিত ইন্টারফেস",
|
|
||||||
SubTitle: "স্বনির্ধারিত Azure বা OpenAI সার্ভিস ব্যবহার করবেন কি?",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "মডেল পরিষেবা প্রদানকারী",
|
|
||||||
SubTitle: "বিভিন্ন পরিষেবা প্রদানকারীতে স্যুইচ করুন",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API কী",
|
|
||||||
SubTitle:
|
|
||||||
"পাসওয়ার্ড অ্যাক্সেস সীমাবদ্ধতা এড়াতে স্বনির্ধারিত OpenAI কী ব্যবহার করুন",
|
|
||||||
Placeholder: "OpenAI API কী",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "ডিফল্ট ঠিকানা বাদে, http(s):// অন্তর্ভুক্ত করতে হবে",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ইন্টারফেস কী",
|
|
||||||
SubTitle:
|
|
||||||
"পাসওয়ার্ড অ্যাক্সেস সীমাবদ্ধতা এড়াতে স্বনির্ধারিত Azure কী ব্যবহার করুন",
|
|
||||||
Placeholder: "Azure API কী",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "উদাহরণ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "ইন্টারফেস সংস্করণ (azure api version)",
|
|
||||||
SubTitle: "নির্দিষ্ট সংস্করণ নির্বাচন করুন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ইন্টারফেস কী",
|
|
||||||
SubTitle:
|
|
||||||
"পাসওয়ার্ড অ্যাক্সেস সীমাবদ্ধতা এড়াতে স্বনির্ধারিত Anthropic কী ব্যবহার করুন",
|
|
||||||
Placeholder: "Anthropic API কী",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "উদাহরণ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "ইন্টারফেস সংস্করণ (claude api version)",
|
|
||||||
SubTitle: "নির্দিষ্ট API সংস্করণ প্রবেশ করুন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API কী",
|
|
||||||
SubTitle: "Google AI থেকে আপনার API কী পান",
|
|
||||||
Placeholder: "আপনার Google AI Studio API কী প্রবেশ করুন",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "টার্মিনাল ঠিকানা",
|
|
||||||
SubTitle: "উদাহরণ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "API সংস্করণ (শুধুমাত্র gemini-pro)",
|
|
||||||
SubTitle: "একটি নির্দিষ্ট API সংস্করণ নির্বাচন করুন",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google সেফটি ফিল্টার স্তর",
|
|
||||||
SubTitle: "বিষয়বস্তু ফিল্টার স্তর সেট করুন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API কী",
|
|
||||||
SubTitle: "স্বনির্ধারিত Baidu API কী ব্যবহার করুন",
|
|
||||||
Placeholder: "Baidu API কী",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "সিক্রেট কী",
|
|
||||||
SubTitle: "স্বনির্ধারিত Baidu সিক্রেট কী ব্যবহার করুন",
|
|
||||||
Placeholder: "Baidu সিক্রেট কী",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "স্বনির্ধারিত সমর্থিত নয়, .env কনফিগারেশনে চলে যান",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ইন্টারফেস কী",
|
|
||||||
SubTitle: "স্বনির্ধারিত ByteDance API কী ব্যবহার করুন",
|
|
||||||
Placeholder: "ByteDance API কী",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "উদাহরণ:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ইন্টারফেস কী",
|
|
||||||
SubTitle: "স্বনির্ধারিত আলিবাবা ক্লাউড API কী ব্যবহার করুন",
|
|
||||||
Placeholder: "Alibaba Cloud API কী",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "ইন্টারফেস ঠিকানা",
|
|
||||||
SubTitle: "উদাহরণ:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "স্বনির্ধারিত মডেল নাম",
|
|
||||||
SubTitle:
|
|
||||||
"স্বনির্ধারিত মডেল বিকল্পগুলি যুক্ত করুন, ইংরেজি কমা দ্বারা আলাদা করুন",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "মডেল (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "যাদুকরিতা (temperature)",
|
Title: "তাপমাত্রা",
|
||||||
SubTitle: "মান বাড়ালে উত্তর বেশি এলোমেলো হবে",
|
SubTitle: "আরতি মান বেশি করলে বেশি এলোমেলো আউটপুট হবে",
|
||||||
},
|
},
|
||||||
TopP: {
|
TopP: {
|
||||||
Title: "নিউক্লিয়ার স্যাম্পলিং (top_p)",
|
Title: "শীর্ষ পি",
|
||||||
SubTitle: "যাদুকরিতা মত, কিন্তু একসাথে পরিবর্তন করবেন না",
|
SubTitle: "তাপমাত্রা সঙ্গে এই মান পরিবর্তন করবেন না",
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "একটি উত্তর সীমা (max_tokens)",
|
Title: "সর্বাধিক টোকেন",
|
||||||
SubTitle: "প্রতি ইন্টারঅ্যাকশনে সর্বাধিক টোকেন সংখ্যা",
|
SubTitle: "ইনপুট টোকেন এবং উৎপাদিত টোকেনের সর্বাধিক দৈর্ঘ্য",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "বিষয়বস্তু তাজা (presence_penalty)",
|
Title: "উপস্থিতির জরিমানা",
|
||||||
SubTitle: "মান বাড়ালে নতুন বিষয়ে প্রসারিত হওয়ার সম্ভাবনা বেশি",
|
SubTitle: "আরতি মান বেশি করলে নতুন বিষয়গুলি সম্ভাব্যতা বাড়াতে পারে",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "ফ্রিকোয়েন্সি পেনাল্টি (frequency_penalty)",
|
Title: "ফ্রিকুয়েন্সি জরিমানা",
|
||||||
SubTitle: "মান বাড়ালে পুনরাবৃত্তি শব্দ কমানোর সম্ভাবনা বেশি",
|
SubTitle:
|
||||||
|
"আরতি মান বাড়ালে একই লাইন পুনরায় ব্যাবহার করার সম্ভাবনা হ্রাস পায়",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "নতুন চ্যাট",
|
DefaultTopic: "নতুন সংলাপ",
|
||||||
BotHello: "আপনার জন্য কিছু করতে পারি?",
|
BotHello: "হ্যালো! আজকে আপনাকে কিভাবে সাহায্য করতে পারি?",
|
||||||
Error: "একটি ত্রুটি ঘটেছে, পরে আবার চেষ্টা করুন",
|
Error: "কিছু নিয়ে ভুল হয়েছে, পরে আবার চেষ্টা করুন।",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"এটি পূর্বের চ্যাটের সারাংশ হিসেবে ব্যবহৃত হবে: " + content,
|
"এটি চ্যাট ইতিহাসের সংক্ষিপ্ত সংকলনের মতো: " + content,
|
||||||
Topic:
|
Topic:
|
||||||
"চার থেকে পাঁচটি শব্দ ব্যবহার করে এই বাক্যের সংক্ষিপ্ত থিম দিন, ব্যাখ্যা, বিরাম চিহ্ন, ভাষা, অতিরিক্ত টেক্সট বা বোল্ড না ব্যবহার করুন। যদি কোনো থিম না থাকে তবে সরাসরি 'বেকার' বলুন",
|
"আমাদের সংলাপটির চার থেকে পাঁচ শব্দের একটি শিরোনাম তৈরি করুন যা আমাদের আলাপের সংক্ষিপ্তসার হিসাবে যোগ হবে না, যেমন অভিবৃত্তি, বিন্যাস, উদ্ধৃতি, পূর্বচালক চিহ্ন, পূর্বরোবক্তির যেকোনো চিহ্ন বা অতিরিক্ত পাঠ। মেয়াদশেষ উদ্ধৃতি চেষ্টা করুন।",
|
||||||
Summarize:
|
Summarize:
|
||||||
"আলোচনার বিষয়বস্তু সংক্ষিপ্তভাবে সারাংশ করুন, পরবর্তী কনটেক্সট প্রম্পট হিসেবে ব্যবহারের জন্য, ২০০ শব্দের মধ্যে সীমাবদ্ধ রাখুন",
|
"২০০ শব্দের লম্বা হয়ে মুহূর্তে আলোচনা সংক্ষেপের রপ্তানি করুন, যেটি ভবিষ্যতের প্রম্পট হিসাবে ব্যবহার করবেন।",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "ক্লিপবোর্ডে লেখা হয়েছে",
|
Success: "ক্লিপবোর্ডে কপি করা হয়েছে",
|
||||||
Failed: "কপি ব্যর্থ হয়েছে, দয়া করে ক্লিপবোর্ড অনুমতি প্রদান করুন",
|
Failed: "কপি ব্যর্থ, অনুমতি প্রদান করার জন্য অনুমতি প্রদান করুন",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "বিষয়বস্তু আপনার ডিরেক্টরিতে ডাউনলোড করা হয়েছে।",
|
|
||||||
Failed: "ডাউনলোড ব্যর্থ হয়েছে।",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `${x}টি পূর্বনির্ধারিত প্রম্পট অন্তর্ভুক্ত`,
|
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||||
Edit: "বর্তমান চ্যাট সেটিংস",
|
Edit: "বর্তমান চ্যাট সেটিংস",
|
||||||
Add: "একটি নতুন চ্যাট যোগ করুন",
|
Add: "একটি প্রম্পট যোগ করুন",
|
||||||
Clear: "কনটেক্সট পরিষ্কার করা হয়েছে",
|
Clear: "সঙ্গতি পরিস্কার করুন",
|
||||||
Revert: "কনটেক্সট পুনরুদ্ধার করুন",
|
Revert: "পূর্ববর্তী অবস্থানে ফিরে যান",
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "প্লাগইন",
|
Name: "প্লাগইন",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "আপনি একজন সহকারী",
|
Sysmessage: "আপনি একটি সহকারী যা",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "মাস্ক",
|
Name: "মাস্ক",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "পূর্বনির্ধারিত চরিত্র মাস্ক",
|
Title: "প্রম্পট টেমপ্লেট",
|
||||||
SubTitle: (count: number) => `${count}টি পূর্বনির্ধারিত চরিত্র সংজ্ঞা`,
|
SubTitle: (count: number) => `${count} টি প্রম্পট টেমপ্লেট`,
|
||||||
Search: "চরিত্র মাস্ক অনুসন্ধান করুন",
|
Search: "টেমপ্লেট অনুসন্ধান করুন",
|
||||||
Create: "নতুন তৈরি করুন",
|
Create: "তৈরি করুন",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `ভিতরে ${count}টি পূর্বনির্ধারিত চ্যাট রয়েছে`,
|
Info: (count: number) => `${count} প্রম্পট`,
|
||||||
Chat: "চ্যাট",
|
Chat: "চ্যাট",
|
||||||
View: "দেখুন",
|
View: "দেখুন",
|
||||||
Edit: "সম্পাদনা করুন",
|
Edit: "সম্পাদন করুন",
|
||||||
Delete: "মুছে ফেলুন",
|
Delete: "মুছে ফেলুন",
|
||||||
DeleteConfirm: "মুছে ফেলার জন্য নিশ্চিত করুন?",
|
DeleteConfirm: "মুছে ফেলতে নিশ্চিত করুন?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`পূর্বনির্ধারিত মাস্ক সম্পাদনা ${readonly ? "(পঠনযোগ্য)" : ""}`,
|
`প্রম্পট টেমপ্লেট সম্পাদন করুন ${readonly ? "(readonly)" : ""}`,
|
||||||
Download: "পূর্বনির্ধারিত ডাউনলোড করুন",
|
Download: "ডাউনলোড করুন",
|
||||||
Clone: "পূর্বনির্ধারিত ক্লোন করুন",
|
Clone: "ক্লোন করুন",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "চরিত্রের চিত্র",
|
Avatar: "বট অবতার",
|
||||||
Name: "চরিত্রের নাম",
|
Name: "বটের নাম",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "গ্লোবাল সেটিংস ব্যবহার করুন",
|
Title: "গ্লোবাল কনফিগ ব্যবহার করুন",
|
||||||
SubTitle: "বর্তমান চ্যাট গ্লোবাল মডেল সেটিংস ব্যবহার করছে কি না",
|
SubTitle: "এই চ্যাটে গ্লোবাল কনফিগ ব্যবহার করুন",
|
||||||
Confirm:
|
Confirm:
|
||||||
"বর্তমান চ্যাটের কাস্টম সেটিংস স্বয়ংক্রিয়ভাবে ওভাররাইট হবে, গ্লোবাল সেটিংস সক্রিয় করতে নিশ্চিত?",
|
"গ্লোবাল কনফিগ দ্বারা কাস্টম কনফিগ ওভাররাইড করতে নিশ্চিত করতে?",
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "পূর্বনির্ধারিত চ্যাট লুকান",
|
Title: "সংশ্লিষ্টতা প্রম্পটগুলি লুকান",
|
||||||
SubTitle:
|
SubTitle: "চ্যাটে সংশ্লিষ্টতা প্রম্পটগুলি দেখাবেন না",
|
||||||
"লুকানোর পরে পূর্বনির্ধারিত চ্যাট চ্যাট ইন্টারফেসে প্রদর্শিত হবে না",
|
|
||||||
},
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "এই মাস্ক শেয়ার করুন",
|
Title: "এই মাস্কটি শেয়ার করুন",
|
||||||
SubTitle: "এই মাস্কের সরাসরি লিঙ্ক তৈরি করুন",
|
SubTitle: "এই মাস্কের একটি লিঙ্ক তৈরি করুন",
|
||||||
Action: "লিঙ্ক কপি করুন",
|
Action: "লিঙ্ক কপি করুন",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "ফিরে যান",
|
Return: "ফিরে যান",
|
||||||
Skip: "ডাইরেক্ট শুরু করুন",
|
Skip: "শুরু করুন",
|
||||||
NotShow: "আবার প্রদর্শন করবেন না",
|
Title: "মাস্ক নির্বাচন করুন",
|
||||||
|
SubTitle: "মাস্কের পিছনে আত্মার সঙ্গে চ্যাট করুন",
|
||||||
|
More: "আরো খুঁজুন",
|
||||||
|
NotShow: "এখনও দেখাবেন না",
|
||||||
ConfirmNoShow:
|
ConfirmNoShow:
|
||||||
"নিশ্চিত যে নিষ্ক্রিয় করবেন? নিষ্ক্রিয় করার পরে সেটিংসে পুনরায় সক্রিয় করা যাবে।",
|
"নিষ্ক্রিয় করতে নিশ্চিত করুন? পরে আপনি এটি সেটিংসে সক্ষম করতে পারবেন।",
|
||||||
Title: "একটি মাস্ক নির্বাচন করুন",
|
|
||||||
SubTitle: "এখন শুরু করুন, মাস্কের পিছনের চিন্তা প্রতিক্রিয়া করুন",
|
|
||||||
More: "সব দেখুন",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "লিঙ্কে অ্যাক্সেস কোড ইতিমধ্যে অন্তর্ভুক্ত রয়েছে, অটো পূরণ করতে চান?",
|
|
||||||
Settings:
|
|
||||||
"লিঙ্কে প্রাক-নির্ধারিত সেটিংস অন্তর্ভুক্ত রয়েছে, অটো পূরণ করতে চান?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "নিশ্চিত করুন",
|
Confirm: "নিশ্চিত করুন",
|
||||||
Cancel: "বাতিল করুন",
|
Cancel: "বাতিল করুন",
|
||||||
Close: "বন্ধ করুন",
|
Close: "বন্ধ করুন",
|
||||||
Create: "নতুন তৈরি করুন",
|
Create: "তৈরি করুন",
|
||||||
Edit: "সম্পাদনা করুন",
|
Edit: "সম্পাদন করুন",
|
||||||
Export: "রপ্তানি করুন",
|
|
||||||
Import: "আমদানি করুন",
|
|
||||||
Sync: "সিঙ্ক",
|
|
||||||
Config: "কনফিগারেশন",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "শুধুমাত্র কনটেক্সট পরিষ্কার করার পরে বার্তাগুলি প্রদর্শিত হবে",
|
|
||||||
},
|
|
||||||
Model: "মডেল",
|
Model: "মডেল",
|
||||||
Messages: "বার্তা",
|
Messages: "বার্তা",
|
||||||
Topic: "থিম",
|
Topic: "টপিক",
|
||||||
Time: "সময়",
|
Time: "সময়",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const cn = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "需要密码",
|
Title: "需要密码",
|
||||||
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
||||||
SubTips: "或者输入你的 OpenAI 或 Google API 密钥",
|
SubTips: "或者输入你的 OpenAI, Google API 或 Anthropic API 密钥",
|
||||||
Input: "在此处填写访问码",
|
Input: "在此处填写访问码",
|
||||||
Confirm: "确认",
|
Confirm: "确认",
|
||||||
Later: "稍后再说",
|
Later: "稍后再说",
|
||||||
@@ -42,7 +42,6 @@ const cn = {
|
|||||||
PinToastAction: "查看",
|
PinToastAction: "查看",
|
||||||
Delete: "删除",
|
Delete: "删除",
|
||||||
Edit: "编辑",
|
Edit: "编辑",
|
||||||
FullScreen: "全屏",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "新建聊天",
|
new: "新建聊天",
|
||||||
@@ -105,10 +104,6 @@ const cn = {
|
|||||||
Toast: "正在生成截图",
|
Toast: "正在生成截图",
|
||||||
Modal: "长按或右键保存图片",
|
Modal: "长按或右键保存图片",
|
||||||
},
|
},
|
||||||
Artifacts: {
|
|
||||||
Title: "分享页面",
|
|
||||||
Error: "分享失败",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "搜索消息",
|
Search: "搜索消息",
|
||||||
@@ -133,7 +128,6 @@ const cn = {
|
|||||||
Settings: {
|
Settings: {
|
||||||
Title: "设置",
|
Title: "设置",
|
||||||
SubTitle: "所有设置选项",
|
SubTitle: "所有设置选项",
|
||||||
ShowPassword: "显示密码",
|
|
||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
@@ -158,11 +152,6 @@ const cn = {
|
|||||||
Title: "字体大小",
|
Title: "字体大小",
|
||||||
SubTitle: "聊天内容的字体大小",
|
SubTitle: "聊天内容的字体大小",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "聊天字体",
|
|
||||||
SubTitle: "聊天内容的字体,若置空则应用全局默认字体",
|
|
||||||
Placeholder: "字体名称",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "注入系统级提示信息",
|
Title: "注入系统级提示信息",
|
||||||
SubTitle: "强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示",
|
SubTitle: "强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示",
|
||||||
@@ -328,7 +317,7 @@ const cn = {
|
|||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "接口密钥",
|
Title: "接口密钥",
|
||||||
SubTitle: "使用自定义 Anthropic Key 绕过密码访问限制",
|
SubTitle: "使用自定义 Anthropic Key 绕过密码访问限制",
|
||||||
Placeholder: "Anthropic API Key",
|
Placeholder: "输入您的 Anthropic API 密钥",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
@@ -357,102 +346,6 @@ const cn = {
|
|||||||
Title: "API 版本(仅适用于 gemini-pro)",
|
Title: "API 版本(仅适用于 gemini-pro)",
|
||||||
SubTitle: "选择一个特定的 API 版本",
|
SubTitle: "选择一个特定的 API 版本",
|
||||||
},
|
},
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google 安全过滤级别",
|
|
||||||
SubTitle: "设置内容过滤级别",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "使用自定义 Baidu API Key",
|
|
||||||
Placeholder: "Baidu API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "使用自定义 Baidu Secret Key",
|
|
||||||
Placeholder: "Baidu Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "不支持自定义前往.env配置",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tencent: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "使用自定义腾讯云API Key",
|
|
||||||
Placeholder: "Tencent API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "使用自定义腾讯云Secret Key",
|
|
||||||
Placeholder: "Tencent Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "不支持自定义前往.env配置",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "接口密钥",
|
|
||||||
SubTitle: "使用自定义 ByteDance API Key",
|
|
||||||
Placeholder: "ByteDance API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "样例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "接口密钥",
|
|
||||||
SubTitle: "使用自定义阿里云API Key",
|
|
||||||
Placeholder: "Alibaba Cloud API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "样例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Moonshot: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "接口密钥",
|
|
||||||
SubTitle: "使用自定义月之暗面API Key",
|
|
||||||
Placeholder: "Moonshot API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "样例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Stability: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "接口密钥",
|
|
||||||
SubTitle: "使用自定义 Stability API Key",
|
|
||||||
Placeholder: "Stability API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "样例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Iflytek: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ApiKey",
|
|
||||||
SubTitle: "从讯飞星火控制台获取的 APIKey",
|
|
||||||
Placeholder: "APIKey",
|
|
||||||
},
|
|
||||||
ApiSecret: {
|
|
||||||
Title: "ApiSecret",
|
|
||||||
SubTitle: "从讯飞星火控制台获取的 APISecret",
|
|
||||||
Placeholder: "APISecret",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "接口地址",
|
|
||||||
SubTitle: "样例:",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
CustomModel: {
|
CustomModel: {
|
||||||
Title: "自定义模型名",
|
Title: "自定义模型名",
|
||||||
@@ -511,10 +404,6 @@ const cn = {
|
|||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "插件",
|
Name: "插件",
|
||||||
Artifacts: "Artifacts",
|
|
||||||
},
|
|
||||||
Discovery: {
|
|
||||||
Name: "发现",
|
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "你是一个助手",
|
Sysmessage: "你是一个助手",
|
||||||
@@ -595,61 +484,6 @@ const cn = {
|
|||||||
Topic: "主题",
|
Topic: "主题",
|
||||||
Time: "时间",
|
Time: "时间",
|
||||||
},
|
},
|
||||||
SdPanel: {
|
|
||||||
Prompt: "画面提示",
|
|
||||||
NegativePrompt: "否定提示",
|
|
||||||
PleaseInput: (name: string) => `请输入${name}`,
|
|
||||||
AspectRatio: "横纵比",
|
|
||||||
ImageStyle: "图像风格",
|
|
||||||
OutFormat: "输出格式",
|
|
||||||
AIModel: "AI模型",
|
|
||||||
ModelVersion: "模型版本",
|
|
||||||
Submit: "提交生成",
|
|
||||||
ParamIsRequired: (name: string) => `${name}不能为空`,
|
|
||||||
Styles: {
|
|
||||||
D3Model: "3D模型",
|
|
||||||
AnalogFilm: "模拟电影",
|
|
||||||
Anime: "动漫",
|
|
||||||
Cinematic: "电影风格",
|
|
||||||
ComicBook: "漫画书",
|
|
||||||
DigitalArt: "数字艺术",
|
|
||||||
Enhance: "增强",
|
|
||||||
FantasyArt: "幻想艺术",
|
|
||||||
Isometric: "等角",
|
|
||||||
LineArt: "线描",
|
|
||||||
LowPoly: "低多边形",
|
|
||||||
ModelingCompound: "建模材料",
|
|
||||||
NeonPunk: "霓虹朋克",
|
|
||||||
Origami: "折纸",
|
|
||||||
Photographic: "摄影",
|
|
||||||
PixelArt: "像素艺术",
|
|
||||||
TileTexture: "贴图",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Sd: {
|
|
||||||
SubTitle: (count: number) => `共 ${count} 条绘画`,
|
|
||||||
Actions: {
|
|
||||||
Params: "查看参数",
|
|
||||||
Copy: "复制提示词",
|
|
||||||
Delete: "删除",
|
|
||||||
Retry: "重试",
|
|
||||||
ReturnHome: "返回首页",
|
|
||||||
History: "查看历史",
|
|
||||||
},
|
|
||||||
EmptyRecord: "暂无绘画记录",
|
|
||||||
Status: {
|
|
||||||
Name: "状态",
|
|
||||||
Success: "成功",
|
|
||||||
Error: "失败",
|
|
||||||
Wait: "等待中",
|
|
||||||
Running: "运行中",
|
|
||||||
},
|
|
||||||
Danger: {
|
|
||||||
Delete: "确认删除?",
|
|
||||||
},
|
|
||||||
GenerateParams: "生成参数",
|
|
||||||
Detail: "详情",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type DeepPartial<T> = T extends object
|
type DeepPartial<T> = T extends object
|
||||||
|
|||||||
@@ -1,547 +1,233 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const cs: PartialLocaleType = {
|
const cs: PartialLocaleType = {
|
||||||
WIP: "V přípravě...",
|
WIP: "V přípravě...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Byl zjištěn neplatný API Key, prosím přejděte na stránku [Nastavení](/#/settings) a zkontrolujte, zda je API Key správně nakonfigurován."
|
"Neoprávněný přístup, zadejte přístupový kód na [stránce](/#/auth) nastavení.",
|
||||||
: "Heslo je nesprávné nebo prázdné, prosím přejděte na stránku [Přihlášení](/#/auth) a zadejte správné heslo, nebo na stránku [Nastavení](/#/settings) a zadejte svůj vlastní OpenAI API Key.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Potřebné heslo",
|
|
||||||
Tips: "Administrátor povolil ověření heslem, prosím zadejte přístupový kód níže",
|
|
||||||
SubTips: "nebo zadejte svůj OpenAI nebo Google API klíč",
|
|
||||||
Input: "Zadejte přístupový kód zde",
|
|
||||||
Confirm: "Potvrdit",
|
|
||||||
Later: "Později",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} konverzací`,
|
ChatItemCount: (count: number) => `${count} zpráv`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Celkem ${count} konverzací`,
|
SubTitle: (count: number) => `${count} zpráv s ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Upravit zprávy",
|
|
||||||
Topic: {
|
|
||||||
Title: "Téma konverzace",
|
|
||||||
SubTitle: "Změnit aktuální téma konverzace",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Zobrazit seznam zpráv",
|
ChatList: "Přejít na seznam chatů",
|
||||||
CompressedHistory: "Zobrazit komprimovanou historii Prompt",
|
CompressedHistory: "Pokyn z komprimované paměti historie",
|
||||||
Export: "Exportovat konverzace",
|
Export: "Exportovat všechny zprávy jako Markdown",
|
||||||
Copy: "Kopírovat",
|
Copy: "Kopírovat",
|
||||||
Stop: "Zastavit",
|
Stop: "Zastavit",
|
||||||
Retry: "Zkusit znovu",
|
Retry: "Zopakovat",
|
||||||
Pin: "Připnout",
|
|
||||||
PinToastContent: "1 konverzace byla připnuta k přednastaveným promptům",
|
|
||||||
PinToastAction: "Zobrazit",
|
|
||||||
Delete: "Smazat",
|
Delete: "Smazat",
|
||||||
Edit: "Upravit",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Přejmenovat chat",
|
||||||
new: "Nová konverzace",
|
Typing: "Píše...",
|
||||||
newm: "Nová konverzace z masky",
|
|
||||||
next: "Další konverzace",
|
|
||||||
prev: "Předchozí konverzace",
|
|
||||||
clear: "Vymazat kontext",
|
|
||||||
del: "Smazat konverzaci",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Zastavit odpověď",
|
|
||||||
ToBottom: "Přejít na nejnovější",
|
|
||||||
Theme: {
|
|
||||||
auto: "Automatické téma",
|
|
||||||
light: "Světelný režim",
|
|
||||||
dark: "Tmavý režim",
|
|
||||||
},
|
|
||||||
Prompt: "Rychlé příkazy",
|
|
||||||
Masks: "Všechny masky",
|
|
||||||
Clear: "Vymazat konverzaci",
|
|
||||||
Settings: "Nastavení konverzace",
|
|
||||||
UploadImage: "Nahrát obrázek",
|
|
||||||
},
|
|
||||||
Rename: "Přejmenovat konverzaci",
|
|
||||||
Typing: "Píše se…",
|
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} odeslat`;
|
var inputHints = `${submitKey} pro odeslání`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter pro nový řádek";
|
inputHints += ", Shift + Enter pro řádkování";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ pro doplnění, : pro příkaz";
|
return inputHints + ", / pro vyhledávání pokynů";
|
||||||
},
|
},
|
||||||
Send: "Odeslat",
|
Send: "Odeslat",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Vymazat paměť",
|
Reset: "Obnovit výchozí",
|
||||||
SaveAs: "Uložit jako masku",
|
SaveAs: "Uložit jako Masku",
|
||||||
},
|
},
|
||||||
IsContext: "Přednastavené prompty",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Sdílet konverzace",
|
Title: "Všechny zprávy",
|
||||||
Copy: "Kopírovat vše",
|
Copy: "Kopírovat vše",
|
||||||
Download: "Stáhnout soubor",
|
Download: "Stáhnout",
|
||||||
Share: "Sdílet na ShareGPT",
|
MessageFromYou: "Zpráva od vás",
|
||||||
MessageFromYou: "Uživatel",
|
MessageFromChatGPT: "Zpráva z ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Formát exportu",
|
|
||||||
SubTitle: "Lze exportovat jako Markdown text nebo PNG obrázek",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Zahrnout kontext masky",
|
|
||||||
SubTitle: "Zobrazit kontext masky ve zprávách",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Vybrat",
|
|
||||||
Preview: "Náhled",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Generování screenshotu",
|
|
||||||
Modal: "Dlouhým stiskem nebo pravým tlačítkem myši uložte obrázek",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Hledat zprávy",
|
|
||||||
All: "Vybrat vše",
|
|
||||||
Latest: "Několik posledních",
|
|
||||||
Clear: "Zrušit výběr",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Historie shrnutí",
|
Title: "Pokyn z paměti",
|
||||||
EmptyContent: "Obsah konverzace je příliš krátký, není třeba shrnovat",
|
EmptyContent: "Zatím nic.",
|
||||||
Send: "Automaticky komprimovat konverzace a odeslat jako kontext",
|
Send: "Odeslat paměť",
|
||||||
Copy: "Kopírovat shrnutí",
|
Copy: "Kopírovat paměť",
|
||||||
Reset: "[nepoužívá se]",
|
Reset: "Obnovit relaci",
|
||||||
ResetConfirm: "Opravdu chcete vymazat historii shrnutí?",
|
ResetConfirm:
|
||||||
|
"Resetováním se vymaže historie aktuálních konverzací i paměť historie pokynů. Opravdu chcete provést obnovu?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Nová konverzace",
|
NewChat: "Nový chat",
|
||||||
DeleteChat: "Opravdu chcete smazat vybranou konverzaci?",
|
DeleteChat: "Potvrzujete smazání vybrané konverzace?",
|
||||||
DeleteToast: "Konverzace byla smazána",
|
DeleteToast: "Chat smazán",
|
||||||
Revert: "Vrátit",
|
Revert: "Zvrátit",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Nastavení",
|
Title: "Nastavení",
|
||||||
SubTitle: "Všechny možnosti nastavení",
|
SubTitle: "Všechna nastavení",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Obnovit všechna nastavení",
|
|
||||||
SubTitle: "Obnovit všechny nastavení na výchozí hodnoty",
|
|
||||||
Action: "Okamžitě obnovit",
|
|
||||||
Confirm: "Opravdu chcete obnovit všechna nastavení?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Smazat všechna data",
|
|
||||||
SubTitle: "Smazat všechny chaty a nastavení",
|
|
||||||
Action: "Okamžitě smazat",
|
|
||||||
Confirm: "Opravdu chcete smazat všechny chaty a nastavení?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // POZOR: pokud chcete přidat nový překlad, prosím, nechte tuto hodnotu jako `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Všechny jazyky",
|
All: "Všechny jazyky",
|
||||||
},
|
},
|
||||||
Avatar: "Profilový obrázek",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Velikost písma",
|
Title: "Velikost písma",
|
||||||
SubTitle: "Velikost písma pro obsah chatu",
|
SubTitle: "Nastavení velikosti písma obsahu chatu",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Chatové Písmo",
|
|
||||||
SubTitle:
|
|
||||||
"Písmo obsahu chatu, ponechejte prázdné pro použití globálního výchozího písma",
|
|
||||||
Placeholder: "Název Písma",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Vložit systémové výzvy",
|
Title: "Vložit systémové prompty",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Automaticky přidat systémovou výzvu simulující ChatGPT na začátek seznamu zpráv pro každý požadavek",
|
"Vynutit přidání simulovaného systémového promptu ChatGPT na začátek seznamu zpráv každého požadavku",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Předzpracování uživatelského vstupu",
|
|
||||||
SubTitle: "Nejnovější zpráva uživatele bude vyplněna do této šablony",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Aktuální verze: ${x}`,
|
Version: (x: string) => `Verze: ${x}`,
|
||||||
IsLatest: "Jste na nejnovější verzi",
|
IsLatest: "Aktuální verze",
|
||||||
CheckUpdate: "Zkontrolovat aktualizace",
|
CheckUpdate: "Zkontrolovat aktualizace",
|
||||||
IsChecking: "Kontrola aktualizací...",
|
IsChecking: "Kontrola aktualizace...",
|
||||||
FoundUpdate: (x: string) => `Nalezena nová verze: ${x}`,
|
FoundUpdate: (x: string) => `Nalezena nová verze: ${x}`,
|
||||||
GoToUpdate: "Přejít na aktualizaci",
|
GoToUpdate: "Aktualizovat",
|
||||||
},
|
},
|
||||||
SendKey: "Klávesa pro odeslání",
|
SendKey: "Odeslat klíč",
|
||||||
Theme: "Téma",
|
Theme: "Téma",
|
||||||
TightBorder: "Režim bez okrajů",
|
TightBorder: "Těsné ohraničení",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Náhledová bublina",
|
Title: "Odesílat chatovací bublinu s náhledem",
|
||||||
SubTitle: "Náhled Markdown obsahu v náhledové bublině",
|
SubTitle: "Zobrazit v náhledu bubliny",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Automatické generování názvu",
|
|
||||||
SubTitle: "Generovat vhodný název na základě obsahu konverzace",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Data na cloudu",
|
|
||||||
NotSyncYet: "Ještě nebylo synchronizováno",
|
|
||||||
Success: "Synchronizace úspěšná",
|
|
||||||
Fail: "Synchronizace selhala",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Nastavení cloudové synchronizace",
|
|
||||||
Check: "Zkontrolovat dostupnost",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Typ synchronizace",
|
|
||||||
SubTitle: "Vyberte oblíbený synchronizační server",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Povolit proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Při synchronizaci v prohlížeči musí být proxy povolena, aby se předešlo problémům s CORS",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Adresa proxy",
|
|
||||||
SubTitle: "Pouze pro interní proxy",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV adresa",
|
|
||||||
UserName: "Uživatelské jméno",
|
|
||||||
Password: "Heslo",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST URL",
|
|
||||||
UserName: "Název zálohy",
|
|
||||||
Password: "UpStash Redis REST Token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Lokální data",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} konverzací, ${overview.message} zpráv, ${overview.prompt} promptů, ${overview.mask} masek`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Import selhal",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Úvodní stránka masky",
|
Title: "Úvodní obrazovka Masek",
|
||||||
SubTitle: "Při zahájení nové konverzace zobrazit úvodní stránku masky",
|
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Skrýt vestavěné masky",
|
|
||||||
SubTitle: "Skrýt vestavěné masky v seznamu všech masek",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Zakázat automatické doplňování promptů",
|
Title: "Deaktivovat automatické dokončování",
|
||||||
SubTitle:
|
SubTitle: "Zadejte / pro spuštění automatického dokončování",
|
||||||
"Automatické doplňování se aktivuje zadáním / na začátku textového pole",
|
|
||||||
},
|
},
|
||||||
List: "Seznam vlastních promptů",
|
List: "Seznam pokynů",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Vestavěné ${builtin} položek, uživatelsky definované ${custom} položek`,
|
`${builtin} vestavěných, ${custom} uživatelských`,
|
||||||
Edit: "Upravit",
|
Edit: "Upravit",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Seznam promptů",
|
Title: "Seznam pokynů",
|
||||||
Add: "Nový",
|
Add: "Přidat pokyn",
|
||||||
Search: "Hledat prompty",
|
Search: "Hledat pokyny",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Upravit prompt",
|
Title: "Editovat pokyn",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Počet historických zpráv",
|
Title: "Počet připojených zpráv",
|
||||||
SubTitle: "Počet historických zpráv zahrnutých v každém požadavku",
|
SubTitle: "Počet odeslaných připojených zpráv na žádost",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Prahová hodnota komprese historických zpráv",
|
Title: "Práh pro kompresi historie",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Když nekomprimované historické zprávy překročí tuto hodnotu, dojde ke kompresi",
|
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Kontrola zůstatku",
|
Title: "Stav účtu",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Tento měsíc použito $${used}, celkový předplatný objem $${total}`;
|
return `Použito tento měsíc $${used}, předplaceno $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Probíhá kontrola…",
|
IsChecking: "Kontroluji...",
|
||||||
Check: "Znovu zkontrolovat",
|
Check: "Zkontrolovat",
|
||||||
NoAccess: "Zadejte API Key nebo přístupové heslo pro zobrazení zůstatku",
|
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Model",
|
||||||
AccessCode: {
|
|
||||||
Title: "Přístupový kód",
|
|
||||||
SubTitle: "Administrátor aktivoval šifrovaný přístup",
|
|
||||||
Placeholder: "Zadejte přístupový kód",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Vlastní rozhraní",
|
|
||||||
SubTitle: "Použít vlastní Azure nebo OpenAI službu",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Poskytovatel modelu",
|
|
||||||
SubTitle: "Přepnout mezi různými poskytovateli",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Použijte vlastní OpenAI Key k obejití přístupového omezení",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle: "Kromě výchozí adresy musí obsahovat http(s)://",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Rozhraní klíč",
|
|
||||||
SubTitle: "Použijte vlastní Azure Key k obejití přístupového omezení",
|
|
||||||
Placeholder: "Azure API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle: "Příklad:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Verze rozhraní (azure api version)",
|
|
||||||
SubTitle: "Vyberte konkrétní verzi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Rozhraní klíč",
|
|
||||||
SubTitle:
|
|
||||||
"Použijte vlastní Anthropic Key k obejití přístupového omezení",
|
|
||||||
Placeholder: "Anthropic API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle: "Příklad:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Verze rozhraní (claude api version)",
|
|
||||||
SubTitle: "Vyberte konkrétní verzi API",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API klíč",
|
|
||||||
SubTitle: "Získejte svůj API klíč od Google AI",
|
|
||||||
Placeholder: "Zadejte svůj Google AI Studio API klíč",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Konečná adresa",
|
|
||||||
SubTitle: "Příklad:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Verze API (pouze pro gemini-pro)",
|
|
||||||
SubTitle: "Vyberte konkrétní verzi API",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Úroveň bezpečnostního filtrování Google",
|
|
||||||
SubTitle: "Nastavit úroveň filtrování obsahu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Použijte vlastní Baidu API Key",
|
|
||||||
Placeholder: "Baidu API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "Použijte vlastní Baidu Secret Key",
|
|
||||||
Placeholder: "Baidu Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle:
|
|
||||||
"Nepodporuje vlastní nastavení, přejděte na .env konfiguraci",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Rozhraní klíč",
|
|
||||||
SubTitle: "Použijte vlastní ByteDance API Key",
|
|
||||||
Placeholder: "ByteDance API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle: "Příklad:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Rozhraní klíč",
|
|
||||||
SubTitle: "Použijte vlastní Alibaba Cloud API Key",
|
|
||||||
Placeholder: "Alibaba Cloud API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresa rozhraní",
|
|
||||||
SubTitle: "Příklad:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Vlastní názvy modelů",
|
|
||||||
SubTitle: "Přidejte možnosti vlastních modelů, oddělené čárkami",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Model (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Náhodnost (temperature)",
|
Title: "Teplota",
|
||||||
SubTitle: "Čím vyšší hodnota, tím náhodnější odpovědi",
|
SubTitle: "Větší hodnota činí výstup náhodnějším",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Jádrové vzorkování (top_p)",
|
|
||||||
SubTitle: "Podobné náhodnosti, ale neměňte spolu s náhodností",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Omezení odpovědi (max_tokens)",
|
Title: "Max. počet tokenů",
|
||||||
SubTitle: "Maximální počet Tokenů použitých v jednom interakci",
|
SubTitle: "Maximální délka vstupního tokenu a generovaných tokenů",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Čerstvost témat (presence_penalty)",
|
Title: "Přítomnostní korekce",
|
||||||
SubTitle:
|
SubTitle: "Větší hodnota zvyšuje pravděpodobnost nových témat.",
|
||||||
"Čím vyšší hodnota, tím větší pravděpodobnost rozšíření na nová témata",
|
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Penalizace frekvence (frequency_penalty)",
|
Title: "Frekvenční penalizace",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Čím vyšší hodnota, tím větší pravděpodobnost snížení opakování slov",
|
"Větší hodnota snižující pravděpodobnost opakování stejného řádku",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Nový chat",
|
DefaultTopic: "Nová konverzace",
|
||||||
BotHello: "Jak vám mohu pomoci?",
|
BotHello: "Ahoj! Jak mohu dnes pomoci?",
|
||||||
Error: "Došlo k chybě, zkuste to prosím znovu později.",
|
Error: "Něco se pokazilo, zkuste to prosím později.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Toto je shrnutí historie chatu jako kontext: " + content,
|
"Toto je shrnutí historie chatu mezi umělou inteligencí a uživatelem v podobě rekapitulace: " +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Použijte čtyři až pět slov pro stručné téma této věty, bez vysvětlení, interpunkce, citoslovcí, nadbytečného textu, bez tučného písma. Pokud téma neexistuje, vraťte pouze 'neformální chat'.",
|
"Vytvořte prosím název o čtyřech až pěti slovech vystihující průběh našeho rozhovoru bez jakýchkoli úvodních slov, interpunkčních znamének, uvozovek, teček, symbolů nebo dalšího textu. Odstraňte uvozovky.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Stručně shrňte obsah konverzace jako kontextový prompt pro budoucí použití, do 200 slov",
|
"Krátce shrň naši diskusi v rozsahu do 200 slov a použij ji jako podnět pro budoucí kontext.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Zkopírováno do schránky",
|
Success: "Zkopírováno do schránky",
|
||||||
Failed: "Kopírování selhalo, prosím, povolte přístup ke schránce",
|
Failed: "Kopírování selhalo, prosím, povolte přístup ke schránce",
|
||||||
},
|
},
|
||||||
Download: {
|
|
||||||
Success: "Obsah byl stažen do vašeho adresáře.",
|
|
||||||
Failed: "Stahování selhalo.",
|
|
||||||
},
|
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Obsahuje ${x} přednastavených promptů`,
|
Toast: (x: any) => `Použití ${x} kontextových pokynů`,
|
||||||
Edit: "Nastavení aktuální konverzace",
|
Edit: "Kontextové a paměťové pokyny",
|
||||||
Add: "Přidat novou konverzaci",
|
Add: "Přidat pokyn",
|
||||||
Clear: "Kontext byl vymazán",
|
|
||||||
Revert: "Obnovit kontext",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Jste asistent",
|
Sysmessage: "Jste asistent, který",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Maska",
|
Name: "Maska",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Přednastavené role masky",
|
Title: "Šablona pokynu",
|
||||||
SubTitle: (count: number) => `${count} definovaných rolí`,
|
SubTitle: (count: number) => `${count} šablon pokynů`,
|
||||||
Search: "Hledat role masky",
|
Search: "Hledat v šablonách",
|
||||||
Create: "Nový",
|
Create: "Vytvořit",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Obsahuje ${count} přednastavených konverzací`,
|
Info: (count: number) => `${count} pokynů`,
|
||||||
Chat: "Chat",
|
Chat: "Chat",
|
||||||
View: "Zobrazit",
|
View: "Zobrazit",
|
||||||
Edit: "Upravit",
|
Edit: "Upravit",
|
||||||
Delete: "Smazat",
|
Delete: "Smazat",
|
||||||
DeleteConfirm: "Opravdu chcete smazat?",
|
DeleteConfirm: "Potvrdit smazání?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Upravit přednastavenou masku ${readonly ? " (jen pro čtení)" : ""}`,
|
`Editovat šablonu pokynu ${readonly ? "(pouze ke čtení)" : ""}`,
|
||||||
Download: "Stáhnout přednastavení",
|
Download: "Stáhnout",
|
||||||
Clone: "Klonovat přednastavení",
|
Clone: "Duplikovat",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Profilový obrázek",
|
Avatar: "Avatar Bota",
|
||||||
Name: "Název role",
|
Name: "Jméno Bota",
|
||||||
Sync: {
|
|
||||||
Title: "Použít globální nastavení",
|
|
||||||
SubTitle: "Použít globální modelová nastavení pro aktuální konverzaci",
|
|
||||||
Confirm:
|
|
||||||
"Vaše vlastní nastavení konverzace bude automaticky přepsáno, opravdu chcete použít globální nastavení?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Skrýt přednastavené konverzace",
|
|
||||||
SubTitle:
|
|
||||||
"Po skrytí se přednastavené konverzace nebudou zobrazovat v chatovém rozhraní",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Sdílet tuto masku",
|
|
||||||
SubTitle: "Generovat přímý odkaz na tuto masku",
|
|
||||||
Action: "Kopírovat odkaz",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Zpět",
|
Return: "Zpět",
|
||||||
Skip: "Začít hned",
|
Skip: "Přeskočit",
|
||||||
NotShow: "Zobrazit už nikdy",
|
Title: "Vyberte Masku",
|
||||||
ConfirmNoShow:
|
SubTitle: "Chatovat s duší za Maskou",
|
||||||
"Opravdu chcete zakázat? Zakázání můžete kdykoli znovu povolit v nastavení.",
|
More: "Najít více",
|
||||||
Title: "Vyberte masku",
|
NotShow: "Nezobrazovat znovu",
|
||||||
SubTitle: "Začněte nyní a konfrontujte se s myslí za maskou",
|
ConfirmNoShow: "Potvrdit zakázání?Můžete jej povolit později v nastavení.",
|
||||||
More: "Zobrazit vše",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Byl detekován přístupový kód v odkazu, chcete jej automaticky vyplnit?",
|
|
||||||
Settings:
|
|
||||||
"Byla detekována přednastavená nastavení v odkazu, chcete je automaticky vyplnit?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Potvrdit",
|
Confirm: "Potvrdit",
|
||||||
Cancel: "Zrušit",
|
Cancel: "Zrušit",
|
||||||
Close: "Zavřít",
|
Close: "Zavřít",
|
||||||
Create: "Nový",
|
Create: "Vytvořit",
|
||||||
Edit: "Upravit",
|
Edit: "Upravit",
|
||||||
Export: "Exportovat",
|
|
||||||
Import: "Importovat",
|
|
||||||
Sync: "Synchronizovat",
|
|
||||||
Config: "Konfigurovat",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Pouze zprávy po vymazání kontextu budou zobrazeny",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Messages: "Zprávy",
|
Messages: "Zprávy",
|
||||||
Topic: "Téma",
|
Topic: "Téma",
|
||||||
|
|||||||
@@ -1,565 +1,235 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const de: PartialLocaleType = {
|
const de: PartialLocaleType = {
|
||||||
WIP: "In Bearbeitung...",
|
WIP: "In Bearbeitung...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Ungültiger API-Schlüssel erkannt. Bitte gehen Sie zur [Einstellungen](/#/settings) Seite, um zu überprüfen, ob der API-Schlüssel korrekt konfiguriert ist."
|
"Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der [Einstellungsseite](/#/auth) ein.",
|
||||||
: "Das Passwort ist falsch oder leer. Bitte gehen Sie zur [Login](/#/auth) Seite, um das richtige Passwort einzugeben, oder fügen Sie Ihren OpenAI API-Schlüssel auf der [Einstellungen](/#/settings) Seite hinzu.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Passwort erforderlich",
|
|
||||||
Tips: "Der Administrator hat die Passwortüberprüfung aktiviert. Bitte geben Sie den Zugangscode unten ein.",
|
|
||||||
SubTips: "Oder geben Sie Ihren OpenAI oder Google API-Schlüssel ein.",
|
|
||||||
Input: "Geben Sie hier den Zugangscode ein",
|
|
||||||
Confirm: "Bestätigen",
|
|
||||||
Later: "Später",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} Gespräche`,
|
ChatItemCount: (count: number) => `${count} Nachrichten`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Insgesamt ${count} Gespräche`,
|
SubTitle: (count: number) => `${count} Nachrichten mit ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Nachricht bearbeiten",
|
|
||||||
Topic: {
|
|
||||||
Title: "Chat-Thema",
|
|
||||||
SubTitle: "Ändern Sie das aktuelle Chat-Thema",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Nachrichtliste anzeigen",
|
ChatList: "Zur Chat-Liste gehen",
|
||||||
CompressedHistory: "Komprimierte Historie anzeigen",
|
CompressedHistory: "Komprimierter Gedächtnis-Prompt",
|
||||||
Export: "Chatverlauf exportieren",
|
Export: "Alle Nachrichten als Markdown exportieren",
|
||||||
Copy: "Kopieren",
|
Copy: "Kopieren",
|
||||||
Stop: "Stoppen",
|
Stop: "Stop",
|
||||||
Retry: "Erneut versuchen",
|
Retry: "Wiederholen",
|
||||||
Pin: "Anheften",
|
Delete: "Delete",
|
||||||
PinToastContent: "1 Gespräch an den voreingestellten Prompt angeheftet",
|
|
||||||
PinToastAction: "Ansehen",
|
|
||||||
Delete: "Löschen",
|
|
||||||
Edit: "Bearbeiten",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Chat umbenennen",
|
||||||
new: "Neues Gespräch",
|
Typing: "Tippen...",
|
||||||
newm: "Neues Gespräch aus Maske erstellen",
|
|
||||||
next: "Nächstes Gespräch",
|
|
||||||
prev: "Vorheriges Gespräch",
|
|
||||||
clear: "Kontext löschen",
|
|
||||||
del: "Gespräch löschen",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Antwort stoppen",
|
|
||||||
ToBottom: "Zum neuesten Beitrag",
|
|
||||||
Theme: {
|
|
||||||
auto: "Automatisches Thema",
|
|
||||||
light: "Helles Thema",
|
|
||||||
dark: "Dunkles Thema",
|
|
||||||
},
|
|
||||||
Prompt: "Schnellbefehle",
|
|
||||||
Masks: "Alle Masken",
|
|
||||||
Clear: "Chat löschen",
|
|
||||||
Settings: "Gesprächseinstellungen",
|
|
||||||
UploadImage: "Bild hochladen",
|
|
||||||
},
|
|
||||||
Rename: "Gespräch umbenennen",
|
|
||||||
Typing: "Tippt…",
|
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} senden`;
|
var inputHints = `${submitKey} um zu Senden`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter für Zeilenumbruch";
|
inputHints += ", Umschalt + Eingabe für Zeilenumbruch";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ für Autovervollständigung, : für Befehle";
|
return inputHints + ", / zum Durchsuchen von Prompts";
|
||||||
},
|
},
|
||||||
Send: "Senden",
|
Send: "Senden",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Erinnerung löschen",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Als Maske speichern",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
IsContext: "Voreingestellter Prompt",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Chatverlauf teilen",
|
Title: "Alle Nachrichten",
|
||||||
Copy: "Alles kopieren",
|
Copy: "Alles kopieren",
|
||||||
Download: "Datei herunterladen",
|
Download: "Herunterladen",
|
||||||
Share: "Auf ShareGPT teilen",
|
MessageFromYou: "Deine Nachricht",
|
||||||
MessageFromYou: "Benutzer",
|
MessageFromChatGPT: "Nachricht von ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Exportformat",
|
|
||||||
SubTitle: "Kann als Markdown-Text oder PNG-Bild exportiert werden",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Maske Kontext einbeziehen",
|
|
||||||
SubTitle: "Soll der Maskenkontext in den Nachrichten angezeigt werden?",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Auswählen",
|
|
||||||
Preview: "Vorschau",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Screenshot wird erstellt",
|
|
||||||
Modal: "Lang drücken oder Rechtsklick, um Bild zu speichern",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Nachrichten suchen",
|
|
||||||
All: "Alles auswählen",
|
|
||||||
Latest: "Neueste",
|
|
||||||
Clear: "Auswahl aufheben",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Historische Zusammenfassung",
|
Title: "Gedächtnis-Prompt",
|
||||||
EmptyContent:
|
EmptyContent: "Noch nichts.",
|
||||||
"Gesprächsinhalte sind zu kurz, keine Zusammenfassung erforderlich",
|
Send: "Gedächtnis senden",
|
||||||
Send: "Chatverlauf automatisch komprimieren und als Kontext senden",
|
Copy: "Gedächtnis kopieren",
|
||||||
Copy: "Zusammenfassung kopieren",
|
Reset: "Sitzung zurücksetzen",
|
||||||
Reset: "[nicht verwendet]",
|
ResetConfirm:
|
||||||
ResetConfirm: "Zusammenfassung löschen bestätigen?",
|
"Das Zurücksetzen löscht den aktuellen Gesprächsverlauf und das Langzeit-Gedächtnis. Möchten Sie wirklich zurücksetzen?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Neues Gespräch",
|
NewChat: "Neuer Chat",
|
||||||
DeleteChat: "Bestätigen Sie das Löschen des ausgewählten Gesprächs?",
|
DeleteChat: "Bestätigen Sie, um das ausgewählte Gespräch zu löschen?",
|
||||||
DeleteToast: "Gespräch gelöscht",
|
DeleteToast: "Chat gelöscht",
|
||||||
Revert: "Rückgängig machen",
|
Revert: "Zurücksetzen",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Einstellungen",
|
Title: "Einstellungen",
|
||||||
SubTitle: "Alle Einstellungsmöglichkeiten",
|
SubTitle: "Alle Einstellungen",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Alle Einstellungen zurücksetzen",
|
|
||||||
SubTitle: "Setzt alle Einstellungen auf die Standardwerte zurück",
|
|
||||||
Action: "Jetzt zurücksetzen",
|
|
||||||
Confirm: "Bestätigen Sie das Zurücksetzen aller Einstellungen?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Alle Daten löschen",
|
|
||||||
SubTitle: "Löscht alle Chats und Einstellungsdaten",
|
|
||||||
Action: "Jetzt löschen",
|
|
||||||
Confirm:
|
|
||||||
"Bestätigen Sie das Löschen aller Chats und Einstellungsdaten?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Sprache", // ACHTUNG: Wenn Sie eine neue Übersetzung hinzufügen möchten, übersetzen Sie diesen Wert bitte nicht, lassen Sie ihn als `Sprache`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Alle Sprachen",
|
All: "Alle Sprachen",
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Schriftgröße",
|
Title: "Schriftgröße",
|
||||||
SubTitle: "Schriftgröße des Chat-Inhalts",
|
SubTitle: "Schriftgröße des Chat-Inhalts anpassen",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Chat-Schriftart",
|
|
||||||
SubTitle:
|
|
||||||
"Schriftart des Chat-Inhalts, leer lassen, um die globale Standardschriftart anzuwenden",
|
|
||||||
Placeholder: "Schriftartname",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Systemweite Eingabeaufforderungen einfügen",
|
Title: "System-Prompts einfügen",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Fügt jeder Nachricht am Anfang der Nachrichtenliste eine simulierte ChatGPT-Systemaufforderung hinzu",
|
"Erzwingt das Hinzufügen eines simulierten systemweiten Prompts von ChatGPT am Anfang der Nachrichtenliste bei jeder Anfrage",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Benutzer-Eingabeverarbeitung",
|
|
||||||
SubTitle:
|
|
||||||
"Die neueste Nachricht des Benutzers wird in diese Vorlage eingefügt",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Aktuelle Version: ${x}`,
|
Version: (x: string) => `Version: ${x}`,
|
||||||
IsLatest: "Bereits die neueste Version",
|
IsLatest: "Neueste Version",
|
||||||
CheckUpdate: "Auf Updates überprüfen",
|
CheckUpdate: "Update prüfen",
|
||||||
IsChecking: "Überprüfe auf Updates...",
|
IsChecking: "Update wird geprüft...",
|
||||||
FoundUpdate: (x: string) => `Neue Version gefunden: ${x}`,
|
FoundUpdate: (x: string) => `Neue Version gefunden: ${x}`,
|
||||||
GoToUpdate: "Zum Update gehen",
|
GoToUpdate: "Aktualisieren",
|
||||||
},
|
},
|
||||||
SendKey: "Sende-Taste",
|
SendKey: "Senden-Taste",
|
||||||
Theme: "Thema",
|
Theme: "Erscheinungsbild",
|
||||||
TightBorder: "Randloser Modus",
|
TightBorder: "Enger Rahmen",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Vorschau-Bubble",
|
Title: "Vorschau-Bubble senden",
|
||||||
SubTitle: "Markdown-Inhalt in der Vorschau-Bubble anzeigen",
|
SubTitle: "Preview markdown in bubble",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Titel automatisch generieren",
|
|
||||||
SubTitle:
|
|
||||||
"Basierend auf dem Chat-Inhalt einen passenden Titel generieren",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Cloud-Daten",
|
|
||||||
NotSyncYet: "Noch nicht synchronisiert",
|
|
||||||
Success: "Synchronisation erfolgreich",
|
|
||||||
Fail: "Synchronisation fehlgeschlagen",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Cloud-Synchronisation konfigurieren",
|
|
||||||
Check: "Verfügbarkeit überprüfen",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Synchronisationstyp",
|
|
||||||
SubTitle: "Wählen Sie den bevorzugten Synchronisationsserver aus",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Proxy aktivieren",
|
|
||||||
SubTitle:
|
|
||||||
"Beim Synchronisieren im Browser muss ein Proxy aktiviert werden, um Cross-Origin-Beschränkungen zu vermeiden",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Proxy-Adresse",
|
|
||||||
SubTitle: "Nur für projektinterne Cross-Origin-Proxy",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV-Adresse",
|
|
||||||
UserName: "Benutzername",
|
|
||||||
Password: "Passwort",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST-Url",
|
|
||||||
UserName: "Sicherungsname",
|
|
||||||
Password: "UpStash Redis REST-Token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Lokale Daten",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} Chats, ${overview.message} Nachrichten, ${overview.prompt} Eingabeaufforderungen, ${overview.mask} Masken`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Import fehlgeschlagen",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Masken-Startseite",
|
Title: "Mask Splash Screen",
|
||||||
SubTitle:
|
SubTitle: "Show a mask splash screen before starting new chat",
|
||||||
"Zeige die Masken-Startseite beim Erstellen eines neuen Chats",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Eingebaute Masken ausblenden",
|
|
||||||
SubTitle: "Blendet eingebaute Masken in allen Maskenlisten aus",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Automatische Eingabeaufforderung deaktivieren",
|
Title: "Autovervollständigung deaktivieren",
|
||||||
SubTitle:
|
SubTitle: "Autovervollständigung mit / starten",
|
||||||
"Geben Sie am Anfang des Eingabefelds / ein, um die automatische Vervollständigung auszulösen",
|
|
||||||
},
|
},
|
||||||
List: "Benutzerdefinierte Eingabeaufforderungsliste",
|
List: "Prompt-Liste",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Eingebaut ${builtin} Stück, Benutzerdefiniert ${custom} Stück`,
|
`${builtin} integriert, ${custom} benutzerdefiniert`,
|
||||||
Edit: "Bearbeiten",
|
Edit: "Bearbeiten",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Eingabeaufforderungsliste",
|
Title: "Prompt List",
|
||||||
Add: "Neu erstellen",
|
Add: "Add One",
|
||||||
Search: "Eingabeaufforderungen suchen",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Eingabeaufforderung bearbeiten",
|
Title: "Edit Prompt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Anzahl der historischen Nachrichten",
|
Title: "Anzahl der angehängten Nachrichten",
|
||||||
SubTitle:
|
SubTitle: "Anzahl der pro Anfrage angehängten gesendeten Nachrichten",
|
||||||
"Anzahl der historischen Nachrichten, die bei jeder Anfrage mitgesendet werden",
|
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Komprimierungsschwelle für historische Nachrichtenlänge",
|
Title: "Schwellenwert für Verlaufskomprimierung",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Wenn die unkomprimierten historischen Nachrichten diesen Wert überschreiten, wird komprimiert",
|
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Guthabenabfrage",
|
Title: "Kontostand",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `In diesem Monat verwendet $${used}, Abonnement insgesamt $${total}`;
|
return `Diesen Monat ausgegeben $${used}, Abonnement $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Wird überprüft…",
|
IsChecking: "Wird überprüft...",
|
||||||
Check: "Erneut überprüfen",
|
Check: "Erneut prüfen",
|
||||||
NoAccess:
|
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
|
||||||
"Geben Sie API-Schlüssel oder Zugangspasswort ein, um das Guthaben einzusehen",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "Zugangscode",
|
|
||||||
SubTitle:
|
|
||||||
"Der Administrator hat die verschlüsselte Zugriffskontrolle aktiviert",
|
|
||||||
Placeholder: "Geben Sie den Zugangscode ein",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Benutzerdefinierte Schnittstelle",
|
|
||||||
SubTitle: "Benutzerdefinierte Azure- oder OpenAI-Dienste verwenden",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Modellanbieter",
|
|
||||||
SubTitle: "Wechseln Sie zu verschiedenen Anbietern",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-Schlüssel",
|
|
||||||
SubTitle:
|
|
||||||
"Verwenden Sie benutzerdefinierten OpenAI-Schlüssel, um Passwortzugangsbeschränkungen zu umgehen",
|
|
||||||
Placeholder: "OpenAI API-Schlüssel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle: "Neben der Standardadresse muss http(s):// enthalten sein",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Schnittstellenschlüssel",
|
|
||||||
SubTitle:
|
|
||||||
"Verwenden Sie benutzerdefinierten Azure-Schlüssel, um Passwortzugangsbeschränkungen zu umgehen",
|
|
||||||
Placeholder: "Azure API-Schlüssel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle: "Beispiel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Schnittstellenversion (azure api version)",
|
|
||||||
SubTitle: "Wählen Sie eine spezifische Teilversion aus",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Schnittstellenschlüssel",
|
|
||||||
SubTitle:
|
|
||||||
"Verwenden Sie benutzerdefinierten Anthropic-Schlüssel, um Passwortzugangsbeschränkungen zu umgehen",
|
|
||||||
Placeholder: "Anthropic API-Schlüssel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle: "Beispiel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Schnittstellenversion (claude api version)",
|
|
||||||
SubTitle: "Wählen Sie eine spezifische API-Version aus",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-Schlüssel",
|
|
||||||
SubTitle: "Holen Sie sich Ihren API-Schlüssel von Google AI",
|
|
||||||
Placeholder: "Geben Sie Ihren Google AI Studio API-Schlüssel ein",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpunktadresse",
|
|
||||||
SubTitle: "Beispiel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "API-Version (nur für gemini-pro)",
|
|
||||||
SubTitle: "Wählen Sie eine spezifische API-Version aus",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google Sicherheitsfilterstufe",
|
|
||||||
SubTitle: "Inhaltfilterstufe einstellen",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-Schlüssel",
|
|
||||||
SubTitle: "Verwenden Sie benutzerdefinierten Baidu API-Schlüssel",
|
|
||||||
Placeholder: "Baidu API-Schlüssel",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Geheimschlüssel",
|
|
||||||
SubTitle: "Verwenden Sie benutzerdefinierten Baidu Geheimschlüssel",
|
|
||||||
Placeholder: "Baidu Geheimschlüssel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle:
|
|
||||||
"Keine benutzerdefinierten Adressen unterstützen, konfigurieren Sie in .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Schnittstellenschlüssel",
|
|
||||||
SubTitle: "Verwenden Sie benutzerdefinierten ByteDance API-Schlüssel",
|
|
||||||
Placeholder: "ByteDance API-Schlüssel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle: "Beispiel:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Schnittstellenschlüssel",
|
|
||||||
SubTitle:
|
|
||||||
"Verwenden Sie benutzerdefinierten Alibaba Cloud API-Schlüssel",
|
|
||||||
Placeholder: "Alibaba Cloud API-Schlüssel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Schnittstellenadresse",
|
|
||||||
SubTitle: "Beispiel:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Benutzerdefinierter Modellname",
|
|
||||||
SubTitle:
|
|
||||||
"Fügen Sie benutzerdefinierte Modelloptionen hinzu, getrennt durch Kommas",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Modell",
|
Model: "Modell",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Zufälligkeit (temperature)",
|
Title: "Temperature", //Temperatur
|
||||||
SubTitle: "Je höher der Wert, desto zufälliger die Antwort",
|
SubTitle: "Ein größerer Wert führt zu zufälligeren Antworten",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Kern-Sampling (top_p)",
|
|
||||||
SubTitle:
|
|
||||||
"Ähnlich der Zufälligkeit, aber nicht zusammen mit Zufälligkeit ändern",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Maximale Token-Anzahl pro Antwort",
|
Title: "Max Tokens", //Maximale Token
|
||||||
SubTitle: "Maximale Anzahl der Tokens pro Interaktion",
|
SubTitle: "Maximale Anzahl der Anfrage- plus Antwort-Token",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Themenfrische (presence_penalty)",
|
Title: "Presence Penalty", //Anwesenheitsstrafe
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Je höher der Wert, desto wahrscheinlicher wird auf neue Themen eingegangen",
|
"Ein größerer Wert erhöht die Wahrscheinlichkeit, dass über neue Themen gesprochen wird",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Häufigkeitsstrafe (frequency_penalty)",
|
Title: "Frequency Penalty", // HäufigkeitStrafe
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Je höher der Wert, desto wahrscheinlicher werden wiederholte Wörter reduziert",
|
"Ein größerer Wert, der die Wahrscheinlichkeit verringert, dass dieselbe Zeile wiederholt wird",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Neuer Chat",
|
DefaultTopic: "Neues Gespräch",
|
||||||
BotHello: "Wie kann ich Ihnen helfen?",
|
BotHello: "Hallo! Wie kann ich Ihnen heute helfen?",
|
||||||
Error:
|
Error:
|
||||||
"Ein Fehler ist aufgetreten, bitte versuchen Sie es später noch einmal",
|
"Etwas ist schief gelaufen, bitte versuchen Sie es später noch einmal.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Dies ist eine Zusammenfassung des bisherigen Chats als Hintergrundinformation: " +
|
"Dies ist eine Zusammenfassung des Chatverlaufs zwischen dem KI und dem Benutzer als Rückblick: " +
|
||||||
content,
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Geben Sie ein kurzes Thema in vier bis fünf Wörtern zurück, ohne Erklärungen, ohne Satzzeichen, ohne Füllwörter, ohne zusätzliche Texte und ohne Fettdruck. Wenn kein Thema vorhanden ist, geben Sie bitte „Allgemeines Gespräch“ zurück.",
|
"Bitte erstellen Sie einen vier- bis fünfwörtigen Titel, der unser Gespräch zusammenfasst, ohne Einleitung, Zeichensetzung, Anführungszeichen, Punkte, Symbole oder zusätzlichen Text. Entfernen Sie Anführungszeichen.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Fassen Sie den Gesprächsinhalt zusammen, um als Kontextaufforderung für den nächsten Schritt zu dienen, halten Sie es unter 200 Zeichen",
|
"Fassen Sie unsere Diskussion kurz in 200 Wörtern oder weniger zusammen, um sie als Pronpt für zukünftige Gespräche zu verwenden.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "In die Zwischenablage geschrieben",
|
Success: "In die Zwischenablage kopiert",
|
||||||
Failed:
|
Failed:
|
||||||
"Kopieren fehlgeschlagen, bitte erlauben Sie Zugriff auf die Zwischenablage",
|
"Kopieren fehlgeschlagen, bitte geben Sie die Berechtigung zum Zugriff auf die Zwischenablage frei",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "Inhalt wurde in Ihrem Verzeichnis heruntergeladen.",
|
|
||||||
Failed: "Download fehlgeschlagen.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Beinhaltet ${x} vordefinierte Eingabeaufforderungen`,
|
Toast: (x: any) => `Mit ${x} Kontext-Prompts`,
|
||||||
Edit: "Aktuelle Gesprächseinstellungen",
|
Edit: "Kontext- und Gedächtnis-Prompts",
|
||||||
Add: "Neues Gespräch hinzufügen",
|
Add: "Hinzufügen",
|
||||||
Clear: "Kontext gelöscht",
|
|
||||||
Revert: "Kontext wiederherstellen",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugins",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Du bist ein Assistent",
|
Sysmessage: "Du bist ein Assistent, der",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Masken",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Vordefinierte Rollenmasken",
|
Title: "Prompt Template",
|
||||||
SubTitle: (count: number) =>
|
SubTitle: (count: number) => `${count} prompt templates`,
|
||||||
`${count} vordefinierte Rollenbeschreibungen`,
|
Search: "Search Templates",
|
||||||
Search: "Rollenmasken suchen",
|
Create: "Create",
|
||||||
Create: "Neu erstellen",
|
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Beinhaltet ${count} vordefinierte Gespräche`,
|
Info: (count: number) => `${count} prompts`,
|
||||||
Chat: "Gespräch",
|
Chat: "Chat",
|
||||||
View: "Anzeigen",
|
View: "View",
|
||||||
Edit: "Bearbeiten",
|
Edit: "Edit",
|
||||||
Delete: "Löschen",
|
Delete: "Delete",
|
||||||
DeleteConfirm: "Bestätigen Sie das Löschen?",
|
DeleteConfirm: "Confirm to delete?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Vordefinierte Maske bearbeiten ${readonly ? "(Nur lesen)" : ""}`,
|
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
|
||||||
Download: "Vorgabe herunterladen",
|
Download: "Download",
|
||||||
Clone: "Vorgabe klonen",
|
Clone: "Clone",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Rollen-Avatar",
|
Avatar: "Bot Avatar",
|
||||||
Name: "Rollenname",
|
Name: "Bot Name",
|
||||||
Sync: {
|
|
||||||
Title: "Globale Einstellungen verwenden",
|
|
||||||
SubTitle:
|
|
||||||
"Soll das aktuelle Gespräch die globalen Modelleinstellungen verwenden?",
|
|
||||||
Confirm:
|
|
||||||
"Die benutzerdefinierten Einstellungen des aktuellen Gesprächs werden automatisch überschrieben. Bestätigen Sie, dass Sie die globalen Einstellungen aktivieren möchten?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Vordefinierte Gespräche ausblenden",
|
|
||||||
SubTitle:
|
|
||||||
"Nach dem Ausblenden werden vordefinierte Gespräche nicht mehr im Chat angezeigt",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Diese Maske teilen",
|
|
||||||
SubTitle: "Generieren Sie einen Direktlink zu dieser Maske",
|
|
||||||
Action: "Link kopieren",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Zurück",
|
Return: "Return",
|
||||||
Skip: "Direkt beginnen",
|
Skip: "Skip",
|
||||||
NotShow: "Nicht mehr anzeigen",
|
Title: "Pick a Mask",
|
||||||
ConfirmNoShow:
|
SubTitle: "Chat with the Soul behind the Mask",
|
||||||
"Bestätigen Sie die Deaktivierung? Nach der Deaktivierung können Sie jederzeit in den Einstellungen wieder aktivieren.",
|
More: "Find More",
|
||||||
Title: "Wählen Sie eine Maske aus",
|
NotShow: "Not Show Again",
|
||||||
SubTitle:
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
"Starten Sie jetzt und lassen Sie sich von den Gedanken hinter der Maske inspirieren",
|
|
||||||
More: "Alle anzeigen",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Ein Zugangscode wurde im Link gefunden. Möchten Sie diesen automatisch einfügen?",
|
|
||||||
Settings:
|
|
||||||
"Vordefinierte Einstellungen wurden im Link gefunden. Möchten Sie diese automatisch einfügen?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Bestätigen",
|
Confirm: "Confirm",
|
||||||
Cancel: "Abbrechen",
|
Cancel: "Cancel",
|
||||||
Close: "Schließen",
|
Close: "Close",
|
||||||
Create: "Neu erstellen",
|
Create: "Create",
|
||||||
Edit: "Bearbeiten",
|
Edit: "Edit",
|
||||||
Export: "Exportieren",
|
|
||||||
Import: "Importieren",
|
|
||||||
Sync: "Synchronisieren",
|
|
||||||
Config: "Konfigurieren",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Nur Nachrichten nach dem Löschen des Kontexts werden angezeigt",
|
|
||||||
},
|
|
||||||
Model: "Modell",
|
Model: "Modell",
|
||||||
Messages: "Nachrichten",
|
Messages: "Nachrichten",
|
||||||
Topic: "Thema",
|
Topic: "Thema",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const en: LocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "Need Access Code",
|
Title: "Need Access Code",
|
||||||
Tips: "Please enter access code below",
|
Tips: "Please enter access code below",
|
||||||
SubTips: "Or enter your OpenAI or Google API Key",
|
SubTips: "Or enter your OpenAI, Google API Key or Anthropic API Key",
|
||||||
Input: "access code",
|
Input: "access code",
|
||||||
Confirm: "Confirm",
|
Confirm: "Confirm",
|
||||||
Later: "Later",
|
Later: "Later",
|
||||||
@@ -44,7 +44,6 @@ const en: LocaleType = {
|
|||||||
PinToastAction: "View",
|
PinToastAction: "View",
|
||||||
Delete: "Delete",
|
Delete: "Delete",
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
FullScreen: "FullScreen",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "Start a new chat",
|
new: "Start a new chat",
|
||||||
@@ -107,10 +106,6 @@ const en: LocaleType = {
|
|||||||
Toast: "Capturing Image...",
|
Toast: "Capturing Image...",
|
||||||
Modal: "Long press or right click to save image",
|
Modal: "Long press or right click to save image",
|
||||||
},
|
},
|
||||||
Artifacts: {
|
|
||||||
Title: "Share Artifacts",
|
|
||||||
Error: "Share Error",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "Search",
|
Search: "Search",
|
||||||
@@ -136,7 +131,6 @@ const en: LocaleType = {
|
|||||||
Settings: {
|
Settings: {
|
||||||
Title: "Settings",
|
Title: "Settings",
|
||||||
SubTitle: "All Settings",
|
SubTitle: "All Settings",
|
||||||
ShowPassword: "ShowPassword",
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "Reset All Settings",
|
Title: "Reset All Settings",
|
||||||
@@ -160,12 +154,6 @@ const en: LocaleType = {
|
|||||||
Title: "Font Size",
|
Title: "Font Size",
|
||||||
SubTitle: "Adjust font size of chat content",
|
SubTitle: "Adjust font size of chat content",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "Chat Font Family",
|
|
||||||
SubTitle:
|
|
||||||
"Font Family of the chat content, leave empty to apply global default font",
|
|
||||||
Placeholder: "Font Family Name",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Inject System Prompts",
|
Title: "Inject System Prompts",
|
||||||
SubTitle: "Inject a global system prompt for every request",
|
SubTitle: "Inject a global system prompt for every request",
|
||||||
@@ -308,7 +296,7 @@ const en: LocaleType = {
|
|||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "OpenAI Endpoint",
|
Title: "OpenAI Endpoint",
|
||||||
SubTitle: "Must start with http(s):// or use /api/openai as default",
|
SubTitle: "Must starts with http(s):// or use /api/openai as default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Azure: {
|
Azure: {
|
||||||
@@ -333,12 +321,12 @@ const en: LocaleType = {
|
|||||||
Title: "Anthropic API Key",
|
Title: "Anthropic API Key",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Use a custom Anthropic Key to bypass password access restrictions",
|
"Use a custom Anthropic Key to bypass password access restrictions",
|
||||||
Placeholder: "Anthropic API Key",
|
Placeholder: "Enter your Anthropic API Key",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "Endpoint Address",
|
Title: "Endpoint Address",
|
||||||
SubTitle: "Example: ",
|
SubTitle: "Example:",
|
||||||
},
|
},
|
||||||
|
|
||||||
ApiVerion: {
|
ApiVerion: {
|
||||||
@@ -346,98 +334,6 @@ const en: LocaleType = {
|
|||||||
SubTitle: "Select and input a specific API version",
|
SubTitle: "Select and input a specific API version",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Baidu API Key",
|
|
||||||
SubTitle: "Use a custom Baidu API Key",
|
|
||||||
Placeholder: "Baidu API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Baidu Secret Key",
|
|
||||||
SubTitle: "Use a custom Baidu Secret Key",
|
|
||||||
Placeholder: "Baidu Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "not supported, configure in .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tencent: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Tencent API Key",
|
|
||||||
SubTitle: "Use a custom Tencent API Key",
|
|
||||||
Placeholder: "Tencent API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Tencent Secret Key",
|
|
||||||
SubTitle: "Use a custom Tencent Secret Key",
|
|
||||||
Placeholder: "Tencent Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "not supported, configure in .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "ByteDance API Key",
|
|
||||||
SubTitle: "Use a custom ByteDance API Key",
|
|
||||||
Placeholder: "ByteDance API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "Example: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Alibaba API Key",
|
|
||||||
SubTitle: "Use a custom Alibaba Cloud API Key",
|
|
||||||
Placeholder: "Alibaba Cloud API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "Example: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Moonshot: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Moonshot API Key",
|
|
||||||
SubTitle: "Use a custom Moonshot API Key",
|
|
||||||
Placeholder: "Moonshot API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "Example: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Stability: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Stability API Key",
|
|
||||||
SubTitle: "Use a custom Stability API Key",
|
|
||||||
Placeholder: "Stability API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "Example: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Iflytek: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Iflytek API Key",
|
|
||||||
SubTitle: "Use a Iflytek API Key",
|
|
||||||
Placeholder: "Iflytek API Key",
|
|
||||||
},
|
|
||||||
ApiSecret: {
|
|
||||||
Title: "Iflytek API Secret",
|
|
||||||
SubTitle: "Use a Iflytek API Secret",
|
|
||||||
Placeholder: "Iflytek API Secret",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endpoint Address",
|
|
||||||
SubTitle: "Example: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
CustomModel: {
|
||||||
Title: "Custom Models",
|
Title: "Custom Models",
|
||||||
SubTitle: "Custom model options, seperated by comma",
|
SubTitle: "Custom model options, seperated by comma",
|
||||||
@@ -451,17 +347,13 @@ const en: LocaleType = {
|
|||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "Endpoint Address",
|
Title: "Endpoint Address",
|
||||||
SubTitle: "Example: ",
|
SubTitle: "Example:",
|
||||||
},
|
},
|
||||||
|
|
||||||
ApiVersion: {
|
ApiVersion: {
|
||||||
Title: "API Version (specific to gemini-pro)",
|
Title: "API Version (specific to gemini-pro)",
|
||||||
SubTitle: "Select a specific API version",
|
SubTitle: "Select a specific API version",
|
||||||
},
|
},
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google Safety Settings",
|
|
||||||
SubTitle: "Select a safety filtering level",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -519,10 +411,6 @@ const en: LocaleType = {
|
|||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Plugin",
|
||||||
Artifacts: "Artifacts",
|
|
||||||
},
|
|
||||||
Discovery: {
|
|
||||||
Name: "Discovery",
|
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "You are an assistant that",
|
Sysmessage: "You are an assistant that",
|
||||||
@@ -598,65 +486,11 @@ const en: LocaleType = {
|
|||||||
Topic: "Topic",
|
Topic: "Topic",
|
||||||
Time: "Time",
|
Time: "Time",
|
||||||
},
|
},
|
||||||
|
|
||||||
URLCommand: {
|
URLCommand: {
|
||||||
Code: "Detected access code from url, confirm to apply? ",
|
Code: "Detected access code from url, confirm to apply? ",
|
||||||
Settings: "Detected settings from url, confirm to apply?",
|
Settings: "Detected settings from url, confirm to apply?",
|
||||||
},
|
},
|
||||||
SdPanel: {
|
|
||||||
Prompt: "Prompt",
|
|
||||||
NegativePrompt: "Negative Prompt",
|
|
||||||
PleaseInput: (name: string) => `Please input ${name}`,
|
|
||||||
AspectRatio: "Aspect Ratio",
|
|
||||||
ImageStyle: "Image Style",
|
|
||||||
OutFormat: "Output Format",
|
|
||||||
AIModel: "AI Model",
|
|
||||||
ModelVersion: "Model Version",
|
|
||||||
Submit: "Submit",
|
|
||||||
ParamIsRequired: (name: string) => `${name} is required`,
|
|
||||||
Styles: {
|
|
||||||
D3Model: "3d-model",
|
|
||||||
AnalogFilm: "analog-film",
|
|
||||||
Anime: "anime",
|
|
||||||
Cinematic: "cinematic",
|
|
||||||
ComicBook: "comic-book",
|
|
||||||
DigitalArt: "digital-art",
|
|
||||||
Enhance: "enhance",
|
|
||||||
FantasyArt: "fantasy-art",
|
|
||||||
Isometric: "isometric",
|
|
||||||
LineArt: "line-art",
|
|
||||||
LowPoly: "low-poly",
|
|
||||||
ModelingCompound: "modeling-compound",
|
|
||||||
NeonPunk: "neon-punk",
|
|
||||||
Origami: "origami",
|
|
||||||
Photographic: "photographic",
|
|
||||||
PixelArt: "pixel-art",
|
|
||||||
TileTexture: "tile-texture",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Sd: {
|
|
||||||
SubTitle: (count: number) => `${count} images`,
|
|
||||||
Actions: {
|
|
||||||
Params: "See Params",
|
|
||||||
Copy: "Copy Prompt",
|
|
||||||
Delete: "Delete",
|
|
||||||
Retry: "Retry",
|
|
||||||
ReturnHome: "Return Home",
|
|
||||||
History: "History",
|
|
||||||
},
|
|
||||||
EmptyRecord: "No images yet",
|
|
||||||
Status: {
|
|
||||||
Name: "Status",
|
|
||||||
Success: "Success",
|
|
||||||
Error: "Error",
|
|
||||||
Wait: "Waiting",
|
|
||||||
Running: "Running",
|
|
||||||
},
|
|
||||||
Danger: {
|
|
||||||
Delete: "Confirm to delete?",
|
|
||||||
},
|
|
||||||
GenerateParams: "Generate Params",
|
|
||||||
Detail: "Detail",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|||||||
@@ -1,565 +1,239 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const es: PartialLocaleType = {
|
const es: PartialLocaleType = {
|
||||||
WIP: "En construcción...",
|
WIP: "En construcción...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Se detectó una clave API inválida. Por favor, ve a la página de [Configuración](/#/settings) para verificar si la clave API está configurada correctamente."
|
"Acceso no autorizado, por favor ingrese el código de acceso en la [página](/#/auth) de configuración.",
|
||||||
: "La contraseña de acceso es incorrecta o está vacía. Por favor, ve a la página de [Iniciar sesión](/#/auth) para ingresar la contraseña correcta, o en la página de [Configuración](/#/settings) para introducir tu propia clave API de OpenAI.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Se requiere contraseña",
|
|
||||||
Tips: "El administrador ha habilitado la verificación de contraseña. Introduce el código de acceso a continuación",
|
|
||||||
SubTips: "O ingresa tu clave API de OpenAI o Google",
|
|
||||||
Input: "Introduce el código de acceso aquí",
|
|
||||||
Confirm: "Confirmar",
|
|
||||||
Later: "Más tarde",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversaciones`,
|
ChatItemCount: (count: number) => `${count} mensajes`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Total de ${count} conversaciones`,
|
SubTitle: (count: number) => `${count} mensajes con ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Editar registro de mensajes",
|
|
||||||
Topic: {
|
|
||||||
Title: "Tema de la conversación",
|
|
||||||
SubTitle: "Cambiar el tema de la conversación actual",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Ver lista de mensajes",
|
ChatList: "Ir a la lista de chats",
|
||||||
CompressedHistory: "Ver historial de Prompts comprimidos",
|
CompressedHistory: "Historial de memoria comprimido",
|
||||||
Export: "Exportar historial de chat",
|
Export: "Exportar todos los mensajes como Markdown",
|
||||||
Copy: "Copiar",
|
Copy: "Copiar",
|
||||||
Stop: "Detener",
|
Stop: "Detener",
|
||||||
Retry: "Reintentar",
|
Retry: "Reintentar",
|
||||||
Pin: "Fijar",
|
Delete: "Delete",
|
||||||
PinToastContent:
|
|
||||||
"Se ha fijado 1 conversación a los prompts predeterminados",
|
|
||||||
PinToastAction: "Ver",
|
|
||||||
Delete: "Eliminar",
|
|
||||||
Edit: "Editar",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Renombrar chat",
|
||||||
new: "Nueva conversación",
|
Typing: "Escribiendo...",
|
||||||
newm: "Nueva conversación desde la máscara",
|
|
||||||
next: "Siguiente conversación",
|
|
||||||
prev: "Conversación anterior",
|
|
||||||
clear: "Limpiar contexto",
|
|
||||||
del: "Eliminar conversación",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Detener respuesta",
|
|
||||||
ToBottom: "Ir al más reciente",
|
|
||||||
Theme: {
|
|
||||||
auto: "Tema automático",
|
|
||||||
light: "Modo claro",
|
|
||||||
dark: "Modo oscuro",
|
|
||||||
},
|
|
||||||
Prompt: "Comandos rápidos",
|
|
||||||
Masks: "Todas las máscaras",
|
|
||||||
Clear: "Limpiar chat",
|
|
||||||
Settings: "Configuración de conversación",
|
|
||||||
UploadImage: "Subir imagen",
|
|
||||||
},
|
|
||||||
Rename: "Renombrar conversación",
|
|
||||||
Typing: "Escribiendo…",
|
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} para enviar`;
|
var inputHints = `Escribe algo y presiona ${submitKey} para enviar`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter para nueva línea";
|
inputHints += ", presiona Shift + Enter para nueva línea";
|
||||||
}
|
}
|
||||||
return (
|
return inputHints;
|
||||||
inputHints + ",/ para activar autocompletado,: para activar comandos"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
Send: "Enviar",
|
Send: "Enviar",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Borrar memoria",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Guardar como máscara",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
IsContext: "Prompt predeterminado",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Compartir historial de chat",
|
Title: "Todos los mensajes",
|
||||||
Copy: "Copiar todo",
|
Copy: "Copiar todo",
|
||||||
Download: "Descargar archivo",
|
Download: "Descargar",
|
||||||
Share: "Compartir en ShareGPT",
|
MessageFromYou: "Mensaje de ti",
|
||||||
MessageFromYou: "Usuario",
|
MessageFromChatGPT: "Mensaje de ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Formato de exportación",
|
|
||||||
SubTitle: "Puedes exportar como texto Markdown o imagen PNG",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Incluir contexto de máscara",
|
|
||||||
SubTitle: "Mostrar contexto de máscara en los mensajes",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Seleccionar",
|
|
||||||
Preview: "Vista previa",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Generando captura de pantalla",
|
|
||||||
Modal: "Mantén presionado o haz clic derecho para guardar la imagen",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Buscar mensajes",
|
|
||||||
All: "Seleccionar todo",
|
|
||||||
Latest: "Últimos mensajes",
|
|
||||||
Clear: "Limpiar selección",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Resumen histórico",
|
Title: "Historial de memoria",
|
||||||
EmptyContent:
|
EmptyContent: "Aún no hay nada.",
|
||||||
"El contenido de la conversación es demasiado corto para resumir",
|
Copy: "Copiar todo",
|
||||||
Send: "Comprimir automáticamente el historial de chat y enviarlo como contexto",
|
Send: "Send Memory",
|
||||||
Copy: "Copiar resumen",
|
Reset: "Reset Session",
|
||||||
Reset: "[no usado]",
|
ResetConfirm:
|
||||||
ResetConfirm: "¿Confirmar para borrar el resumen histórico?",
|
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Nueva conversación",
|
NewChat: "Nuevo chat",
|
||||||
DeleteChat: "¿Confirmar la eliminación de la conversación seleccionada?",
|
DeleteChat: "¿Confirmar eliminación de la conversación seleccionada?",
|
||||||
DeleteToast: "Conversación eliminada",
|
DeleteToast: "Chat Deleted",
|
||||||
Revert: "Deshacer",
|
Revert: "Revert",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Configuración",
|
Title: "Configuración",
|
||||||
SubTitle: "Todas las opciones de configuración",
|
SubTitle: "Todas las configuraciones",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Restablecer todas las configuraciones",
|
|
||||||
SubTitle:
|
|
||||||
"Restablecer todas las configuraciones a los valores predeterminados",
|
|
||||||
Action: "Restablecer ahora",
|
|
||||||
Confirm: "¿Confirmar el restablecimiento de todas las configuraciones?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Eliminar todos los datos",
|
|
||||||
SubTitle: "Eliminar todos los chats y datos de configuración",
|
|
||||||
Action: "Eliminar ahora",
|
|
||||||
Confirm:
|
|
||||||
"¿Confirmar la eliminación de todos los chats y datos de configuración?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATENCIÓN: si deseas agregar una nueva traducción, por favor no traduzcas este valor, déjalo como `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Todos los idiomas",
|
All: "Todos los idiomas",
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Tamaño de fuente",
|
Title: "Tamaño de fuente",
|
||||||
SubTitle: "Tamaño de la fuente del contenido del chat",
|
SubTitle: "Ajustar el tamaño de fuente del contenido del chat",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Fuente del Chat",
|
|
||||||
SubTitle:
|
|
||||||
"Fuente del contenido del chat, dejar vacío para aplicar la fuente predeterminada global",
|
|
||||||
Placeholder: "Nombre de la Fuente",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Inyectar mensajes del sistema",
|
Title: "Inyectar Prompts del Sistema",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Forzar la adición de un mensaje del sistema simulado de ChatGPT al principio de cada lista de mensajes",
|
"Agregar forzosamente un prompt de sistema simulado de ChatGPT al comienzo de la lista de mensajes en cada solicitud",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Preprocesamiento de entrada del usuario",
|
|
||||||
SubTitle: "El último mensaje del usuario se rellenará en esta plantilla",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Versión actual: ${x}`,
|
Version: (x: string) => `Versión: ${x}`,
|
||||||
IsLatest: "Ya estás en la última versión",
|
IsLatest: "Última versión",
|
||||||
CheckUpdate: "Buscar actualizaciones",
|
CheckUpdate: "Buscar actualizaciones",
|
||||||
IsChecking: "Buscando actualizaciones...",
|
IsChecking: "Buscando actualizaciones...",
|
||||||
FoundUpdate: (x: string) => `Nueva versión encontrada: ${x}`,
|
FoundUpdate: (x: string) => `Se encontró una nueva versión: ${x}`,
|
||||||
GoToUpdate: "Ir a actualizar",
|
GoToUpdate: "Actualizar",
|
||||||
},
|
},
|
||||||
SendKey: "Tecla de enviar",
|
SendKey: "Tecla de envío",
|
||||||
Theme: "Tema",
|
Theme: "Tema",
|
||||||
TightBorder: "Modo sin borde",
|
TightBorder: "Borde ajustado",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Vista previa del globo",
|
Title: "Enviar burbuja de vista previa",
|
||||||
SubTitle:
|
SubTitle: "Preview markdown in bubble",
|
||||||
"Previsualiza el contenido Markdown en un globo de vista previa",
|
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Generar título automáticamente",
|
|
||||||
SubTitle: "Generar un título adecuado basado en el contenido del chat",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Datos en la nube",
|
|
||||||
NotSyncYet: "Aún no se ha sincronizado",
|
|
||||||
Success: "Sincronización exitosa",
|
|
||||||
Fail: "Sincronización fallida",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Configurar sincronización en la nube",
|
|
||||||
Check: "Verificar disponibilidad",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Tipo de sincronización",
|
|
||||||
SubTitle: "Selecciona el servidor de sincronización preferido",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Habilitar proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Debes habilitar el proxy para sincronizar en el navegador y evitar restricciones de CORS",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Dirección del proxy",
|
|
||||||
SubTitle: "Solo para el proxy CORS incluido en este proyecto",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "Dirección WebDAV",
|
|
||||||
UserName: "Nombre de usuario",
|
|
||||||
Password: "Contraseña",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "URL de REST de UpStash Redis",
|
|
||||||
UserName: "Nombre de respaldo",
|
|
||||||
Password: "Token de REST de UpStash Redis",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Datos locales",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} conversaciones, ${overview.message} mensajes, ${overview.prompt} prompts, ${overview.mask} máscaras`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Importación fallida",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Pantalla de inicio de máscara",
|
Title: "Mask Splash Screen",
|
||||||
SubTitle:
|
SubTitle: "Show a mask splash screen before starting new chat",
|
||||||
"Mostrar la pantalla de inicio de la máscara al iniciar un nuevo chat",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Ocultar máscaras integradas",
|
|
||||||
SubTitle:
|
|
||||||
"Ocultar las máscaras integradas en todas las listas de máscaras",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Deshabilitar autocompletado de prompts",
|
Title: "Desactivar autocompletado",
|
||||||
SubTitle:
|
SubTitle: "Escribe / para activar el autocompletado",
|
||||||
"Escribe / al principio del campo de entrada para activar el autocompletado",
|
|
||||||
},
|
},
|
||||||
List: "Lista de prompts personalizados",
|
List: "Lista de autocompletado",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Integrados ${builtin}, definidos por el usuario ${custom}`,
|
`${builtin} incorporado, ${custom} definido por el usuario`,
|
||||||
Edit: "Editar",
|
Edit: "Editar",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Lista de prompts",
|
Title: "Prompt List",
|
||||||
Add: "Nuevo",
|
Add: "Add One",
|
||||||
Search: "Buscar prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Editar prompt",
|
Title: "Edit Prompt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Número de mensajes históricos adjuntos",
|
Title: "Cantidad de mensajes adjuntos",
|
||||||
SubTitle: "Número de mensajes históricos enviados con cada solicitud",
|
SubTitle: "Número de mensajes enviados adjuntos por solicitud",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Umbral de compresión de mensajes históricos",
|
Title: "Umbral de compresión de historial",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Cuando los mensajes históricos no comprimidos superan este valor, se realizará la compresión",
|
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Consulta de saldo",
|
Title: "Saldo de la cuenta",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Saldo usado este mes: $${used}, total suscrito: $${total}`;
|
return `Usado $${used}, subscription $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Verificando…",
|
IsChecking: "Comprobando...",
|
||||||
Check: "Revisar de nuevo",
|
Check: "Comprobar de nuevo",
|
||||||
NoAccess:
|
NoAccess: "Introduzca la clave API para comprobar el saldo",
|
||||||
"Introduce la clave API o la contraseña de acceso para ver el saldo",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Modelo",
|
||||||
AccessCode: {
|
|
||||||
Title: "Contraseña de acceso",
|
|
||||||
SubTitle: "El administrador ha habilitado el acceso encriptado",
|
|
||||||
Placeholder: "Introduce la contraseña de acceso",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Interfaz personalizada",
|
|
||||||
SubTitle: "¿Usar servicios personalizados de Azure u OpenAI?",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Proveedor de modelos",
|
|
||||||
SubTitle: "Cambiar entre diferentes proveedores",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave API",
|
|
||||||
SubTitle:
|
|
||||||
"Usa una clave API de OpenAI personalizada para omitir la restricción de acceso por contraseña",
|
|
||||||
Placeholder: "Clave API de OpenAI",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle:
|
|
||||||
"Debe incluir http(s):// además de la dirección predeterminada",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave de interfaz",
|
|
||||||
SubTitle:
|
|
||||||
"Usa una clave de Azure personalizada para omitir la restricción de acceso por contraseña",
|
|
||||||
Placeholder: "Clave API de Azure",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle: "Ejemplo:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versión de la interfaz (versión de api de azure)",
|
|
||||||
SubTitle: "Selecciona una versión específica",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave de interfaz",
|
|
||||||
SubTitle:
|
|
||||||
"Usa una clave de Anthropic personalizada para omitir la restricción de acceso por contraseña",
|
|
||||||
Placeholder: "Clave API de Anthropic",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle: "Ejemplo:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versión de la interfaz (versión de claude api)",
|
|
||||||
SubTitle: "Selecciona una versión específica de la API",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave API",
|
|
||||||
SubTitle: "Obtén tu clave API de Google AI",
|
|
||||||
Placeholder: "Introduce tu clave API de Google AI Studio",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle: "Ejemplo:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Versión de la API (solo para gemini-pro)",
|
|
||||||
SubTitle: "Selecciona una versión específica de la API",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Nivel de filtrado de seguridad de Google",
|
|
||||||
SubTitle: "Configura el nivel de filtrado de contenido",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave API",
|
|
||||||
SubTitle: "Usa una clave API de Baidu personalizada",
|
|
||||||
Placeholder: "Clave API de Baidu",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Clave secreta",
|
|
||||||
SubTitle: "Usa una clave secreta de Baidu personalizada",
|
|
||||||
Placeholder: "Clave secreta de Baidu",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle:
|
|
||||||
"No admite personalización, dirígete a .env para configurarlo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave de interfaz",
|
|
||||||
SubTitle: "Usa una clave API de ByteDance personalizada",
|
|
||||||
Placeholder: "Clave API de ByteDance",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle: "Ejemplo:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clave de interfaz",
|
|
||||||
SubTitle: "Usa una clave API de Alibaba Cloud personalizada",
|
|
||||||
Placeholder: "Clave API de Alibaba Cloud",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Dirección del endpoint",
|
|
||||||
SubTitle: "Ejemplo:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Nombre del modelo personalizado",
|
|
||||||
SubTitle:
|
|
||||||
"Agrega opciones de modelos personalizados, separados por comas",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Modelo (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Aleatoriedad (temperature)",
|
Title: "Temperatura",
|
||||||
SubTitle: "Cuanto mayor sea el valor, más aleatorio será el resultado",
|
SubTitle: "Un valor mayor genera una salida más aleatoria",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Muestreo por núcleo (top_p)",
|
|
||||||
SubTitle: "Similar a la aleatoriedad, pero no cambies ambos a la vez",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Límite de tokens por respuesta (max_tokens)",
|
Title: "Máximo de tokens",
|
||||||
SubTitle: "Número máximo de tokens utilizados en una sola interacción",
|
SubTitle: "Longitud máxima de tokens de entrada y tokens generados",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Novedad de temas (presence_penalty)",
|
Title: "Penalización de presencia",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Cuanto mayor sea el valor, más probable es que se amplíen a nuevos temas",
|
"Un valor mayor aumenta la probabilidad de hablar sobre nuevos temas",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Penalización de frecuencia (frequency_penalty)",
|
Title: "Penalización de frecuencia",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Cuanto mayor sea el valor, más probable es que se reduzcan las palabras repetidas",
|
"Un valor mayor que disminuye la probabilidad de repetir la misma línea",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Nuevo chat",
|
DefaultTopic: "Nueva conversación",
|
||||||
BotHello: "¿En qué puedo ayudarte?",
|
BotHello: "¡Hola! ¿Cómo puedo ayudarte hoy?",
|
||||||
Error: "Hubo un error, inténtalo de nuevo más tarde",
|
Error: "Algo salió mal, por favor intenta nuevamente más tarde.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Este es un resumen del chat histórico como referencia: " + content,
|
"Este es un resumen del historial del chat entre la IA y el usuario como recapitulación: " +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Devuelve un tema breve de esta frase en cuatro a cinco palabras, sin explicación, sin puntuación, sin muletillas, sin texto adicional, sin negritas. Si no hay tema, devuelve 'charlas casuales'",
|
"Por favor, genera un título de cuatro a cinco palabras que resuma nuestra conversación sin ningún inicio, puntuación, comillas, puntos, símbolos o texto adicional. Elimina las comillas que lo envuelven.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Resume brevemente el contenido de la conversación para usar como un prompt de contexto, manteniéndolo dentro de 200 palabras",
|
"Resuma nuestra discusión brevemente en 200 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Copiado al portapapeles",
|
Success: "Copiado al portapapeles",
|
||||||
Failed: "Error al copiar, por favor otorga permisos al portapapeles",
|
Failed:
|
||||||
},
|
"La copia falló, por favor concede permiso para acceder al portapapeles",
|
||||||
Download: {
|
|
||||||
Success: "Contenido descargado en tu directorio.",
|
|
||||||
Failed: "Error al descargar.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Contiene ${x} prompts predefinidos`,
|
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||||
Edit: "Configuración del chat actual",
|
Edit: "Contextual and Memory Prompts",
|
||||||
Add: "Agregar una conversación",
|
Add: "Add One",
|
||||||
Clear: "Contexto borrado",
|
|
||||||
Revert: "Restaurar contexto",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Complemento",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Eres un asistente",
|
Sysmessage: "Eres un asistente que",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Máscara",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Máscaras de rol predefinidas",
|
Title: "Prompt Template",
|
||||||
SubTitle: (count: number) => `${count} definiciones de rol predefinidas`,
|
SubTitle: (count: number) => `${count} prompt templates`,
|
||||||
Search: "Buscar máscara de rol",
|
Search: "Search Templates",
|
||||||
Create: "Crear nuevo",
|
Create: "Create",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Contiene ${count} conversaciones predefinidas`,
|
Info: (count: number) => `${count} prompts`,
|
||||||
Chat: "Chat",
|
Chat: "Chat",
|
||||||
View: "Ver",
|
View: "View",
|
||||||
Edit: "Editar",
|
Edit: "Edit",
|
||||||
Delete: "Eliminar",
|
Delete: "Delete",
|
||||||
DeleteConfirm: "¿Confirmar eliminación?",
|
DeleteConfirm: "Confirm to delete?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Editar máscara predefinida ${readonly ? "(solo lectura)" : ""}`,
|
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
|
||||||
Download: "Descargar predefinido",
|
Download: "Download",
|
||||||
Clone: "Clonar predefinido",
|
Clone: "Clone",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Avatar del rol",
|
Avatar: "Bot Avatar",
|
||||||
Name: "Nombre del rol",
|
Name: "Bot Name",
|
||||||
Sync: {
|
|
||||||
Title: "Usar configuración global",
|
|
||||||
SubTitle:
|
|
||||||
"¿Usar la configuración global del modelo para la conversación actual?",
|
|
||||||
Confirm:
|
|
||||||
"La configuración personalizada de la conversación actual se sobrescribirá automáticamente, ¿confirmar habilitar la configuración global?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Ocultar conversaciones predefinidas",
|
|
||||||
SubTitle:
|
|
||||||
"Las conversaciones predefinidas ocultas no aparecerán en la interfaz de chat",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Compartir esta máscara",
|
|
||||||
SubTitle: "Generar un enlace directo a esta máscara",
|
|
||||||
Action: "Copiar enlace",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Regresar",
|
Return: "Return",
|
||||||
Skip: "Comenzar ahora",
|
Skip: "Skip",
|
||||||
NotShow: "No mostrar más",
|
Title: "Pick a Mask",
|
||||||
ConfirmNoShow:
|
SubTitle: "Chat with the Soul behind the Mask",
|
||||||
"¿Confirmar desactivación? Puedes reactivar en la configuración en cualquier momento.",
|
More: "Find More",
|
||||||
Title: "Selecciona una máscara",
|
NotShow: "Not Show Again",
|
||||||
SubTitle: "Comienza ahora y colisiona con la mente detrás de la máscara",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
More: "Ver todo",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Detectado un código de acceso en el enlace, ¿deseas autocompletarlo?",
|
|
||||||
Settings:
|
|
||||||
"Detectada configuración predefinida en el enlace, ¿deseas autocompletarla?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Confirmar",
|
Confirm: "Confirm",
|
||||||
Cancel: "Cancelar",
|
Cancel: "Cancel",
|
||||||
Close: "Cerrar",
|
Close: "Close",
|
||||||
Create: "Crear",
|
Create: "Create",
|
||||||
Edit: "Editar",
|
Edit: "Edit",
|
||||||
Export: "Exportar",
|
|
||||||
Import: "Importar",
|
|
||||||
Sync: "Sincronizar",
|
|
||||||
Config: "Configurar",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Solo se mostrarán los mensajes después de borrar el contexto",
|
|
||||||
},
|
|
||||||
Model: "Modelo",
|
Model: "Modelo",
|
||||||
Messages: "Mensajes",
|
Messages: "Mensajes",
|
||||||
Topic: "Tema",
|
Topic: "Tema",
|
||||||
Time: "Hora",
|
Time: "Time",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,544 +1,294 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const fr: PartialLocaleType = {
|
const fr: PartialLocaleType = {
|
||||||
WIP: "Prochainement...",
|
WIP: "Prochainement...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Clé API invalide détectée. Veuillez vérifier si la clé API est correctement configurée dans la page [Paramètres](/#/settings)."
|
"Accès non autorisé, veuillez saisir le code d'accès dans la [page](/#/auth) des paramètres.",
|
||||||
: "Le mot de passe d'accès est incorrect ou manquant. Veuillez entrer le mot de passe d'accès correct sur la page [Connexion](/#/auth) ou entrer votre propre clé API OpenAI sur la page [Paramètres](/#/settings).",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Mot de passe requis",
|
|
||||||
Tips: "L'administrateur a activé la vérification par mot de passe. Veuillez entrer le code d'accès ci-dessous",
|
|
||||||
SubTips: "Ou entrez votre clé API OpenAI ou Google",
|
|
||||||
Input: "Entrez le code d'accès ici",
|
|
||||||
Confirm: "Confirmer",
|
|
||||||
Later: "Plus tard",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversations`,
|
ChatItemCount: (count: number) => `${count} messages en total`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Total de ${count} conversations`,
|
SubTitle: (count: number) => `${count} messages échangés avec ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Modifier l'historique des messages",
|
|
||||||
Topic: {
|
|
||||||
Title: "Sujet de la discussion",
|
|
||||||
SubTitle: "Modifier le sujet de la discussion actuel",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Voir la liste des messages",
|
ChatList: "Aller à la liste de discussion",
|
||||||
CompressedHistory: "Voir l'historique des prompts compressés",
|
CompressedHistory: "Mémoire d'historique compressée Prompt",
|
||||||
Export: "Exporter l'historique de la discussion",
|
Export: "Exporter tous les messages en tant que Markdown",
|
||||||
Copy: "Copier",
|
Copy: "Copier",
|
||||||
Stop: "Arrêter",
|
Stop: "Arrêter",
|
||||||
Retry: "Réessayer",
|
Retry: "Réessayer",
|
||||||
Pin: "Épingler",
|
|
||||||
PinToastContent: "1 conversation épinglée aux prompts prédéfinis",
|
|
||||||
PinToastAction: "Voir",
|
|
||||||
Delete: "Supprimer",
|
Delete: "Supprimer",
|
||||||
|
Pin: "Épingler",
|
||||||
|
PinToastContent: "Épingler 2 messages à des messages contextuels",
|
||||||
|
PinToastAction: "Voir",
|
||||||
Edit: "Modifier",
|
Edit: "Modifier",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "Nouvelle discussion",
|
new: "Commencer une nouvelle conversation",
|
||||||
newm: "Créer une discussion à partir du masque",
|
newm: "Démarrer une nouvelle conversation avec un assistant",
|
||||||
next: "Discussion suivante",
|
next: "Conversation suivante",
|
||||||
prev: "Discussion précédente",
|
prev: "Conversation précédente",
|
||||||
clear: "Effacer le contexte",
|
clear: "Effacer le contexte",
|
||||||
del: "Supprimer la discussion",
|
del: "Supprimer la Conversation",
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
Stop: "Arrêter la réponse",
|
Stop: "Stop",
|
||||||
ToBottom: "Aller au plus récent",
|
ToBottom: "Au dernier",
|
||||||
Theme: {
|
Theme: {
|
||||||
auto: "Thème automatique",
|
auto: "Auto",
|
||||||
light: "Mode clair",
|
light: "Thème clair",
|
||||||
dark: "Mode sombre",
|
dark: "Thème sombre",
|
||||||
},
|
},
|
||||||
Prompt: "Commandes rapides",
|
Prompt: "Instructions",
|
||||||
Masks: "Tous les masques",
|
Masks: "Assistants",
|
||||||
Clear: "Effacer la discussion",
|
Clear: "Effacer le contexte",
|
||||||
Settings: "Paramètres de la discussion",
|
Settings: "Réglages",
|
||||||
UploadImage: "Télécharger une image",
|
|
||||||
},
|
},
|
||||||
Rename: "Renommer la discussion",
|
Rename: "Renommer la conversation",
|
||||||
Typing: "En train d'écrire…",
|
Typing: "En train d'écrire…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} pour envoyer`;
|
var inputHints = `Appuyez sur ${submitKey} pour envoyer`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter pour passer à la ligne";
|
inputHints += ", Shift + Enter pour insérer un saut de ligne";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ pour compléter, : pour déclencher des commandes";
|
return inputHints + ", / pour rechercher des prompts";
|
||||||
},
|
},
|
||||||
Send: "Envoyer",
|
Send: "Envoyer",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Effacer la mémoire",
|
Reset: "Restaurer les paramètres par défaut",
|
||||||
SaveAs: "Enregistrer comme masque",
|
SaveAs: "Enregistrer en tant que masque",
|
||||||
},
|
},
|
||||||
IsContext: "Prompt prédéfini",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Partager l'historique des discussions",
|
Title: "Tous les messages",
|
||||||
Copy: "Tout copier",
|
Copy: "Tout sélectionner",
|
||||||
Download: "Télécharger le fichier",
|
Download: "Télécharger",
|
||||||
Share: "Partager sur ShareGPT",
|
MessageFromYou: "Message de votre part",
|
||||||
MessageFromYou: "Utilisateur",
|
MessageFromChatGPT: "Message de ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Format d'exportation",
|
|
||||||
SubTitle: "Vous pouvez exporter en texte Markdown ou en image PNG",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Inclure le contexte du masque",
|
|
||||||
SubTitle: "Afficher le contexte du masque dans les messages",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Sélectionner",
|
|
||||||
Preview: "Aperçu",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Génération de la capture d'écran",
|
|
||||||
Modal:
|
|
||||||
"Appuyez longuement ou faites un clic droit pour enregistrer l'image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Rechercher des messages",
|
|
||||||
All: "Tout sélectionner",
|
|
||||||
Latest: "Derniers messages",
|
|
||||||
Clear: "Effacer la sélection",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Résumé historique",
|
Title: "Prompt mémoire",
|
||||||
EmptyContent: "Le contenu de la discussion est trop court pour être résumé",
|
EmptyContent: "Rien encore.",
|
||||||
Send: "Compresser automatiquement l'historique des discussions et l'envoyer comme contexte",
|
Send: "Envoyer la mémoire",
|
||||||
Copy: "Copier le résumé",
|
Copy: "Copier la mémoire",
|
||||||
Reset: "[unused]",
|
Reset: "Réinitialiser la session",
|
||||||
ResetConfirm: "Confirmer la suppression du résumé historique ?",
|
ResetConfirm:
|
||||||
|
"La réinitialisation supprimera l'historique de la conversation actuelle ainsi que la mémoire de l'historique. Êtes-vous sûr de vouloir procéder à la réinitialisation?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Nouvelle discussion",
|
NewChat: "Nouvelle discussion",
|
||||||
DeleteChat: "Confirmer la suppression de la discussion sélectionnée ?",
|
DeleteChat: "Confirmer la suppression de la conversation sélectionnée ?",
|
||||||
DeleteToast: "Discussion supprimée",
|
DeleteToast: "Conversation supprimée",
|
||||||
Revert: "Annuler",
|
Revert: "Revenir en arrière",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Paramètres",
|
Title: "Paramètres",
|
||||||
SubTitle: "Toutes les options de configuration",
|
SubTitle: "Toutes les configurations",
|
||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "Réinitialiser tous les paramètres",
|
Title: "Restaurer les paramètres",
|
||||||
SubTitle:
|
SubTitle: "Restaurer les paramètres par défaut",
|
||||||
"Réinitialiser toutes les options de configuration aux valeurs par défaut",
|
Action: "Reinitialiser",
|
||||||
Action: "Réinitialiser maintenant",
|
Confirm: "Confirmer la réinitialisation des paramètres?",
|
||||||
Confirm: "Confirmer la réinitialisation de tous les paramètres ?",
|
|
||||||
},
|
},
|
||||||
Clear: {
|
Clear: {
|
||||||
Title: "Effacer toutes les données",
|
Title: "Supprimer toutes les données",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Effacer toutes les discussions et les données de configuration",
|
"Effacer toutes les données, y compris les conversations et les paramètres",
|
||||||
Action: "Effacer maintenant",
|
Action: "Supprimer",
|
||||||
Confirm:
|
Confirm: "Confirmer la suppression de toutes les données?",
|
||||||
"Confirmer l'effacement de toutes les discussions et données de configuration ?",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "Language", // ATTENTION : si vous souhaitez ajouter une nouvelle traduction, ne traduisez pas cette valeur, laissez-la sous forme de `Language`
|
||||||
All: "Toutes les langues",
|
All: "Toutes les langues",
|
||||||
},
|
},
|
||||||
|
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Taille de la police",
|
Title: "Taille des polices",
|
||||||
SubTitle: "Taille de la police pour le contenu des discussions",
|
SubTitle: "Ajuste la taille de police du contenu de la conversation",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Police de Chat",
|
|
||||||
SubTitle:
|
|
||||||
"Police du contenu du chat, laissez vide pour appliquer la police par défaut globale",
|
|
||||||
Placeholder: "Nom de la Police",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Injecter des invites système",
|
Title: "Injecter des invites système",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Ajouter de manière forcée une invite système simulée de ChatGPT au début de chaque liste de messages",
|
"Ajoute de force une invite système simulée de ChatGPT au début de la liste des messages pour chaque demande",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "Prétraitement des entrées utilisateur",
|
Title: "Template",
|
||||||
SubTitle:
|
SubTitle: "Le message le plus récent sera ajouté à ce template.",
|
||||||
"Le dernier message de l'utilisateur sera intégré dans ce modèle",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Version actuelle : ${x}`,
|
Version: (x: string) => `Version : ${x}`,
|
||||||
IsLatest: "Vous avez la dernière version",
|
IsLatest: "Dernière version",
|
||||||
CheckUpdate: "Vérifier les mises à jour",
|
CheckUpdate: "Vérifier la mise à jour",
|
||||||
IsChecking: "Vérification des mises à jour en cours...",
|
IsChecking: "Vérification de la mise à jour...",
|
||||||
FoundUpdate: (x: string) => `Nouvelle version trouvée : ${x}`,
|
FoundUpdate: (x: string) => `Nouvelle version disponible : ${x}`,
|
||||||
GoToUpdate: "Aller à la mise à jour",
|
GoToUpdate: "Mise à jour",
|
||||||
},
|
},
|
||||||
SendKey: "Touche d'envoi",
|
SendKey: "Clé d'envoi",
|
||||||
Theme: "Thème",
|
Theme: "Thème",
|
||||||
TightBorder: "Mode sans bordure",
|
TightBorder: "Bordure serrée",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Bulle d'aperçu",
|
Title: "Aperçu de l'envoi dans une bulle",
|
||||||
SubTitle: "Aperçu du contenu Markdown dans la bulle d'aperçu",
|
SubTitle: "Aperçu du Markdown dans une bulle",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Génération automatique de titres",
|
|
||||||
SubTitle:
|
|
||||||
"Générer un titre approprié en fonction du contenu de la discussion",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Données cloud",
|
|
||||||
NotSyncYet: "Pas encore synchronisé",
|
|
||||||
Success: "Synchronisation réussie",
|
|
||||||
Fail: "Échec de la synchronisation",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Configurer la synchronisation cloud",
|
|
||||||
Check: "Vérifier la disponibilité",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Type de synchronisation",
|
|
||||||
SubTitle: "Choisissez le serveur de synchronisation préféré",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Activer le proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Lors de la synchronisation dans le navigateur, le proxy doit être activé pour éviter les restrictions de domaine croisé",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Adresse du proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Uniquement pour le proxy de domaine croisé fourni par le projet",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "Adresse WebDAV",
|
|
||||||
UserName: "Nom d'utilisateur",
|
|
||||||
Password: "Mot de passe",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "URL REST Redis UpStash",
|
|
||||||
UserName: "Nom de sauvegarde",
|
|
||||||
Password: "Token REST Redis UpStash",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Données locales",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} discussions, ${overview.message} messages, ${overview.prompt} invites, ${overview.mask} masques`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Échec de l'importation",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Page de démarrage du masque",
|
Title: "Écran de masque",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Afficher la page de démarrage du masque lors de la création d'une nouvelle discussion",
|
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
|
||||||
},
|
},
|
||||||
Builtin: {
|
Builtin: {
|
||||||
Title: "Masquer les masques intégrés",
|
Title: "Masquer Les Assistants Intégrés",
|
||||||
SubTitle:
|
SubTitle: "Masquer les assistants intégrés par défaut",
|
||||||
"Masquer les masques intégrés dans toutes les listes de masques",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Désactiver la complétion automatique des invites",
|
Title: "Désactiver la saisie semi-automatique",
|
||||||
SubTitle:
|
SubTitle: "Appuyez sur / pour activer la saisie semi-automatique",
|
||||||
"Saisir / au début de la zone de texte pour déclencher la complétion automatique",
|
|
||||||
},
|
},
|
||||||
List: "Liste des invites personnalisées",
|
List: "Liste de prompts",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} intégrées, ${custom} définies par l'utilisateur`,
|
`${builtin} intégré, ${custom} personnalisé`,
|
||||||
Edit: "Modifier",
|
Edit: "Modifier",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Liste des invites",
|
Title: "Liste de prompts",
|
||||||
Add: "Créer",
|
Add: "Ajouter un élément",
|
||||||
Search: "Rechercher des invites",
|
Search: "Rechercher des prompts",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Modifier les invites",
|
Title: "Modifier le prompt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Nombre de messages historiques",
|
Title: "Nombre de messages joints",
|
||||||
SubTitle: "Nombre de messages historiques envoyés avec chaque demande",
|
SubTitle: "Nombre de messages envoyés attachés par demande",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Seuil de compression des messages historiques",
|
Title: "Seuil de compression de l'historique",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Compresser les messages historiques lorsque leur longueur dépasse cette valeur",
|
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Vérification du solde",
|
Title: "Solde du compte",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Utilisé ce mois-ci : $${used}, Total d'abonnement : $${total}`;
|
return `Épuisé ce mois-ci $${used}, abonnement $${total}`;
|
||||||
},
|
|
||||||
IsChecking: "Vérification en cours…",
|
|
||||||
Check: "Re-vérifier",
|
|
||||||
NoAccess:
|
|
||||||
"Entrez la clé API ou le mot de passe d'accès pour vérifier le solde",
|
|
||||||
},
|
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "Mot de passe d'accès",
|
|
||||||
SubTitle: "L'administrateur a activé l'accès sécurisé",
|
|
||||||
Placeholder: "Veuillez entrer le mot de passe d'accès",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Interface personnalisée",
|
|
||||||
SubTitle: "Utiliser un service Azure ou OpenAI personnalisé",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Fournisseur de modèle",
|
|
||||||
SubTitle: "Changer de fournisseur de service",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé API",
|
|
||||||
SubTitle:
|
|
||||||
"Utiliser une clé OpenAI personnalisée pour contourner les restrictions d'accès par mot de passe",
|
|
||||||
Placeholder: "Clé API OpenAI",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Doit inclure http(s):// en dehors de l'adresse par défaut",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé d'interface",
|
|
||||||
SubTitle:
|
|
||||||
"Utiliser une clé Azure personnalisée pour contourner les restrictions d'accès par mot de passe",
|
|
||||||
Placeholder: "Clé API Azure",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Exemple :",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Version de l'interface (version API azure)",
|
|
||||||
SubTitle: "Choisissez une version spécifique",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé d'interface",
|
|
||||||
SubTitle:
|
|
||||||
"Utiliser une clé Anthropic personnalisée pour contourner les restrictions d'accès par mot de passe",
|
|
||||||
Placeholder: "Clé API Anthropic",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Exemple :",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Version de l'interface (version API claude)",
|
|
||||||
SubTitle: "Choisissez une version spécifique de l'API",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé API",
|
|
||||||
SubTitle: "Obtenez votre clé API Google AI",
|
|
||||||
Placeholder: "Entrez votre clé API Google AI Studio",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Exemple :",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Version de l'API (pour gemini-pro uniquement)",
|
|
||||||
SubTitle: "Choisissez une version spécifique de l'API",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Niveau de filtrage de sécurité Google",
|
|
||||||
SubTitle: "Définir le niveau de filtrage du contenu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé API",
|
|
||||||
SubTitle: "Utiliser une clé API Baidu personnalisée",
|
|
||||||
Placeholder: "Clé API Baidu",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Clé secrète",
|
|
||||||
SubTitle: "Utiliser une clé secrète Baidu personnalisée",
|
|
||||||
Placeholder: "Clé secrète Baidu",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle:
|
|
||||||
"Non pris en charge pour les configurations personnalisées dans .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé d'interface",
|
|
||||||
SubTitle: "Utiliser une clé API ByteDance personnalisée",
|
|
||||||
Placeholder: "Clé API ByteDance",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Exemple :",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Clé d'interface",
|
|
||||||
SubTitle: "Utiliser une clé API Alibaba Cloud personnalisée",
|
|
||||||
Placeholder: "Clé API Alibaba Cloud",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Adresse de l'interface",
|
|
||||||
SubTitle: "Exemple :",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Nom du modèle personnalisé",
|
|
||||||
SubTitle:
|
|
||||||
"Ajouter des options de modèles personnalisés, séparées par des virgules",
|
|
||||||
},
|
},
|
||||||
|
IsChecking: "Vérification...",
|
||||||
|
Check: "Vérifier",
|
||||||
|
NoAccess: "Entrez la clé API pour vérifier le solde",
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "Modèle",
|
Model: "Modèle",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Aléatoire (temperature)",
|
Title: "Température",
|
||||||
SubTitle: "Plus la valeur est élevée, plus les réponses sont aléatoires",
|
SubTitle: "Une valeur plus élevée rendra les réponses plus aléatoires",
|
||||||
},
|
},
|
||||||
TopP: {
|
TopP: {
|
||||||
Title: "Échantillonnage par noyau (top_p)",
|
Title: "Top P",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Semblable à l'aléatoire, mais ne pas modifier en même temps que l'aléatoire",
|
"Ne modifiez pas à moins que vous ne sachiez ce que vous faites",
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Limite de réponse unique (max_tokens)",
|
Title: "Limite de Tokens",
|
||||||
SubTitle: "Nombre maximal de tokens utilisés pour une interaction unique",
|
SubTitle: "Longueur maximale des tokens d'entrée et des tokens générés",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Nouveauté du sujet (presence_penalty)",
|
Title: "Pénalité de présence",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Plus la valeur est élevée, plus il est probable d'élargir aux nouveaux sujets",
|
"Une valeur plus élevée augmentera la probabilité d'introduire de nouveaux sujets",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Pénalité de fréquence (frequency_penalty)",
|
Title: "Pénalité de fréquence",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Plus la valeur est élevée, plus il est probable de réduire les répétitions",
|
"Une valeur plus élevée diminuant la probabilité de répéter la même ligne",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Nouvelle discussion",
|
DefaultTopic: "Nouvelle conversation",
|
||||||
BotHello: "Comment puis-je vous aider ?",
|
BotHello: "Bonjour ! Comment puis-je vous aider aujourd'hui ?",
|
||||||
Error: "Une erreur est survenue, veuillez réessayer plus tard",
|
Error: "Quelque chose s'est mal passé, veuillez réessayer plus tard.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Voici le résumé de la discussion précédente : " + content,
|
"Ceci est un résumé de l'historique des discussions entre l'IA et l'utilisateur : " +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Utilisez quatre à cinq mots pour retourner le sujet succinct de cette phrase, sans explication, sans ponctuation, sans interjections, sans texte superflu, sans gras. Si aucun sujet, retournez simplement « discussion informelle »",
|
"Veuillez générer un titre de quatre à cinq mots résumant notre conversation sans introduction, ponctuation, guillemets, points, symboles ou texte supplémentaire. Supprimez les guillemets inclus.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Faites un résumé succinct de la discussion, à utiliser comme prompt de contexte ultérieur, en moins de 200 mots",
|
"Résumez brièvement nos discussions en 200 mots ou moins pour les utiliser comme prompt de contexte futur.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Copié dans le presse-papiers",
|
Success: "Copié dans le presse-papiers",
|
||||||
Failed: "Échec de la copie, veuillez autoriser l'accès au presse-papiers",
|
Failed:
|
||||||
},
|
"La copie a échoué, veuillez accorder l'autorisation d'accès au presse-papiers",
|
||||||
Download: {
|
|
||||||
Success: "Le contenu a été téléchargé dans votre répertoire.",
|
|
||||||
Failed: "Échec du téléchargement.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Contient ${x} invites prédéfinies`,
|
Toast: (x: any) => `Avec ${x} contextes de prompts`,
|
||||||
Edit: "Paramètres de la discussion actuelle",
|
Edit: "Contextes et mémoires de prompts",
|
||||||
Add: "Ajouter une discussion",
|
Add: "Ajouter un prompt",
|
||||||
Clear: "Contexte effacé",
|
|
||||||
Revert: "Restaurer le contexte",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Extension",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Vous êtes un assistant",
|
Sysmessage: "Eres un asistente que",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Masque",
|
Name: "Masque",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Masques de rôle prédéfinis",
|
Title: "Modèle de prompt",
|
||||||
SubTitle: (count: number) => `${count} définitions de rôle prédéfinies`,
|
SubTitle: (count: number) => `${count} modèles de prompts`,
|
||||||
Search: "Rechercher des masques de rôle",
|
Search: "Rechercher des modèles",
|
||||||
Create: "Créer",
|
Create: "Créer",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Contient ${count} discussions prédéfinies`,
|
Info: (count: number) => `${count} prompts`,
|
||||||
Chat: "Discussion",
|
Chat: "Discussion",
|
||||||
View: "Voir",
|
View: "Vue",
|
||||||
Edit: "Modifier",
|
Edit: "Modifier",
|
||||||
Delete: "Supprimer",
|
Delete: "Supprimer",
|
||||||
DeleteConfirm: "Confirmer la suppression ?",
|
DeleteConfirm: "Confirmer la suppression?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Modifier le masque prédéfini ${readonly ? " (lecture seule)" : ""}`,
|
`Modifier le modèle de prompt ${readonly ? "(en lecture seule)" : ""}`,
|
||||||
Download: "Télécharger le masque",
|
Download: "Télécharger",
|
||||||
Clone: "Cloner le masque",
|
Clone: "Dupliquer",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Avatar du rôle",
|
Avatar: "Avatar de lassistant",
|
||||||
Name: "Nom du rôle",
|
Name: "Nom de lassistant",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "Utiliser les paramètres globaux",
|
Title: "Utiliser la configuration globale",
|
||||||
SubTitle:
|
SubTitle: "Utiliser la configuration globale dans cette conversation",
|
||||||
"Cette discussion utilise-t-elle les paramètres du modèle globaux ?",
|
Confirm: "Voulez-vous definir votre configuration personnalisée ?",
|
||||||
Confirm:
|
|
||||||
"Les paramètres personnalisés de cette discussion seront automatiquement remplacés. Confirmer l'activation des paramètres globaux ?",
|
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "Masquer les discussions prédéfinies",
|
Title: "Masquer les invites contextuelles",
|
||||||
SubTitle:
|
SubTitle: "Ne pas afficher les instructions contextuelles dans le chat",
|
||||||
"Les discussions prédéfinies ne seront pas affichées dans l'interface de discussion après masquage",
|
|
||||||
},
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "Partager ce masque",
|
Title: "Partager ce masque",
|
||||||
SubTitle: "Générer un lien direct pour ce masque",
|
SubTitle: "Générer un lien vers ce masque",
|
||||||
Action: "Copier le lien",
|
Action: "Copier le lien",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Retour",
|
Return: "Retour",
|
||||||
Skip: "Commencer directement",
|
Skip: "Passer",
|
||||||
NotShow: "Ne plus afficher",
|
Title: "Choisir un assitant",
|
||||||
|
SubTitle: "Discutez avec l'âme derrière le masque",
|
||||||
|
More: "En savoir plus",
|
||||||
|
NotShow: "Ne pas afficher à nouveau",
|
||||||
ConfirmNoShow:
|
ConfirmNoShow:
|
||||||
"Confirmer la désactivation ? Vous pourrez réactiver cette option à tout moment dans les paramètres.",
|
"Confirmez-vous vouloir désactiver cela? Vous pouvez le réactiver plus tard dans les paramètres.",
|
||||||
Title: "Choisir un masque",
|
|
||||||
SubTitle: "Commencez maintenant, rencontrez les pensées derrière le masque",
|
|
||||||
More: "Voir tout",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Code d'accès détecté dans le lien, souhaitez-vous le remplir automatiquement ?",
|
|
||||||
Settings:
|
|
||||||
"Paramètres prédéfinis détectés dans le lien, souhaitez-vous les remplir automatiquement ?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
@@ -546,17 +296,9 @@ const fr: PartialLocaleType = {
|
|||||||
Cancel: "Annuler",
|
Cancel: "Annuler",
|
||||||
Close: "Fermer",
|
Close: "Fermer",
|
||||||
Create: "Créer",
|
Create: "Créer",
|
||||||
Edit: "Modifier",
|
Edit: "Éditer",
|
||||||
Export: "Exporter",
|
|
||||||
Import: "Importer",
|
|
||||||
Sync: "Synchroniser",
|
|
||||||
Config: "Configurer",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title:
|
|
||||||
"Seuls les messages après avoir effacé le contexte seront affichés",
|
|
||||||
},
|
|
||||||
Model: "Modèle",
|
Model: "Modèle",
|
||||||
Messages: "Messages",
|
Messages: "Messages",
|
||||||
Topic: "Sujet",
|
Topic: "Sujet",
|
||||||
|
|||||||
@@ -1,261 +1,228 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const id: PartialLocaleType = {
|
const id: PartialLocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "API Key tidak valid terdeteksi, silakan periksa apakah API Key telah dikonfigurasi dengan benar di halaman [Pengaturan](/#/settings)."
|
"Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
|
||||||
: "Kata sandi akses tidak benar atau kosong, silakan masukkan kata sandi akses yang benar di halaman [Masuk](/#/auth), atau masukkan OpenAI API Key Anda di halaman [Pengaturan](/#/settings).",
|
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Kebutuhan Kata Sandi",
|
Title: "Diperlukan Kode Akses",
|
||||||
Tips: "Administrator telah mengaktifkan verifikasi kata sandi, silakan masukkan kode akses di bawah ini",
|
Tips: "Masukkan kode akses di bawah",
|
||||||
SubTips: "Atau masukkan kunci API OpenAI atau Google Anda",
|
SubTips: "Atau masukkan kunci API OpenAI Anda",
|
||||||
Input: "Masukkan kode akses di sini",
|
Input: "Kode Akses",
|
||||||
Confirm: "Konfirmasi",
|
Confirm: "Konfirmasi",
|
||||||
Later: "Nanti",
|
Later: "Nanti",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} percakapan`,
|
ChatItemCount: (count: number) => `${count} pesan`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Total ${count} percakapan`,
|
SubTitle: (count: number) => `${count} pesan`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Edit Riwayat Pesan",
|
|
||||||
Topic: {
|
|
||||||
Title: "Topik Obrolan",
|
|
||||||
SubTitle: "Ubah topik obrolan saat ini",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Lihat daftar pesan",
|
ChatList: "Buka Daftar Chat",
|
||||||
CompressedHistory: "Lihat riwayat Prompt yang dikompresi",
|
CompressedHistory: "Ekspor Riwayat Terkompresi",
|
||||||
Export: "Ekspor riwayat obrolan",
|
Export: "Ekspor Semua Pesan sebagai Markdown",
|
||||||
Copy: "Salin",
|
Copy: "Salin",
|
||||||
Stop: "Berhenti",
|
Stop: "Berhenti",
|
||||||
Retry: "Coba lagi",
|
Retry: "Coba Lagi",
|
||||||
Pin: "Sematkan",
|
Pin: "Pin",
|
||||||
PinToastContent: "1 percakapan telah disematkan ke prompt default",
|
PinToastContent: "2 pesan telah ditandai",
|
||||||
PinToastAction: "Lihat",
|
PinToastAction: "Lihat",
|
||||||
Delete: "Hapus",
|
Delete: "Hapus",
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "Obrolan Baru",
|
new: "Mulai Chat Baru",
|
||||||
newm: "Buat Obrolan Baru dari Masker",
|
newm: "Mulai Chat Baru dengan Masks",
|
||||||
next: "Obrolan Berikutnya",
|
next: "Chat Selanjutnya",
|
||||||
prev: "Obrolan Sebelumnya",
|
prev: "Chat Sebelumnya",
|
||||||
clear: "Hapus Konteks",
|
clear: "Bersihkan Percakapan",
|
||||||
del: "Hapus Obrolan",
|
del: "Hapus Chat",
|
||||||
},
|
},
|
||||||
InputActions: {
|
InputActions: {
|
||||||
Stop: "Hentikan Respons",
|
Stop: "Berhenti",
|
||||||
ToBottom: "Gulir ke bawah",
|
ToBottom: "Ke Bagian Bawah",
|
||||||
Theme: {
|
Theme: {
|
||||||
auto: "Tema Otomatis",
|
auto: "Otomatis",
|
||||||
light: "Mode Terang",
|
light: "Tema Terang",
|
||||||
dark: "Mode Gelap",
|
dark: "Tema Gelap",
|
||||||
},
|
},
|
||||||
Prompt: "Perintah Cepat",
|
Prompt: "Prompts",
|
||||||
Masks: "Semua Masker",
|
Masks: "Masks",
|
||||||
Clear: "Hapus Obrolan",
|
Clear: "Bersihkan Percakapan",
|
||||||
Settings: "Pengaturan Obrolan",
|
Settings: "Pengaturan",
|
||||||
UploadImage: "Unggah Gambar",
|
|
||||||
},
|
},
|
||||||
Rename: "Ganti Nama Obrolan",
|
Rename: "Ubah Nama Chat",
|
||||||
Typing: "Sedang Mengetik…",
|
Typing: "Mengetik...",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} kirim`;
|
var inputHints = `${submitKey} untuk mengirim`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter untuk baris baru";
|
inputHints += ", Shift + Enter untuk membalut";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ untuk melengkapi, : untuk memicu perintah";
|
return (
|
||||||
|
inputHints + ", / untuk mencari prompt, : untuk menggunakan perintah"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
Send: "Kirim",
|
Send: "Kirim",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Hapus Memori",
|
Reset: "Reset ke Default",
|
||||||
SaveAs: "Simpan sebagai Masker",
|
SaveAs: "Simpan sebagai Masks",
|
||||||
},
|
},
|
||||||
IsContext: "Prompt Default",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Bagikan Riwayat Obrolan",
|
Title: "Ekspor Pesan",
|
||||||
Copy: "Salin Semua",
|
Copy: "Salin Semua",
|
||||||
Download: "Unduh File",
|
Download: "Unduh",
|
||||||
|
MessageFromYou: "Pesan dari Anda",
|
||||||
|
MessageFromChatGPT: "Pesan dari ChatGPT",
|
||||||
Share: "Bagikan ke ShareGPT",
|
Share: "Bagikan ke ShareGPT",
|
||||||
MessageFromYou: "Pengguna",
|
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
Format: {
|
||||||
Title: "Format Ekspor",
|
Title: "Format Ekspor",
|
||||||
SubTitle: "Dapat mengekspor teks Markdown atau gambar PNG",
|
SubTitle: "Markdown atau Gambar PNG",
|
||||||
},
|
},
|
||||||
IncludeContext: {
|
IncludeContext: {
|
||||||
Title: "Sertakan Konteks Masker",
|
Title: "Sertakan Konteks",
|
||||||
SubTitle: "Apakah akan menampilkan konteks masker dalam pesan",
|
SubTitle: "Apakah akan menyertakan masks",
|
||||||
},
|
},
|
||||||
Steps: {
|
Steps: {
|
||||||
Select: "Pilih",
|
Select: "Pilih",
|
||||||
Preview: "Prabaca",
|
Preview: "Pratinjau",
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Sedang Membuat Screenshot",
|
|
||||||
Modal: "Tekan lama atau klik kanan untuk menyimpan gambar",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "Cari Pesan",
|
Search: "Cari",
|
||||||
All: "Pilih Semua",
|
All: "Pilih Semua",
|
||||||
Latest: "Beberapa Terbaru",
|
Latest: "Pilih Terbaru",
|
||||||
Clear: "Hapus Pilihan",
|
Clear: "Bersihkan",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Ringkasan Sejarah",
|
Title: "Prompt Memori",
|
||||||
EmptyContent: "Isi percakapan terlalu pendek, tidak perlu dirangkum",
|
EmptyContent: "Belum ada yang tersedia.",
|
||||||
Send: "Otomatis kompres riwayat obrolan dan kirim sebagai konteks",
|
Send: "Kirim Memori",
|
||||||
Copy: "Salin Ringkasan",
|
Copy: "Salin Memori",
|
||||||
Reset: "[unused]",
|
Reset: "Reset",
|
||||||
ResetConfirm: "Konfirmasi untuk menghapus ringkasan sejarah?",
|
ResetConfirm:
|
||||||
|
"Jika Anda mereset, riwayat obrolan saat ini dan memori historis akan dihapus. Apakah Anda yakin ingin melakukan reset?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Obrolan Baru",
|
NewChat: "Obrolan Baru",
|
||||||
DeleteChat: "Konfirmasi untuk menghapus percakapan yang dipilih?",
|
DeleteChat: "Anda yakin ingin menghapus percakapan yang dipilih?",
|
||||||
DeleteToast: "Percakapan telah dihapus",
|
DeleteToast: "Percakapan telah dihapus",
|
||||||
Revert: "Batalkan",
|
Revert: "Kembali",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Pengaturan",
|
Title: "Pengaturan",
|
||||||
SubTitle: "Semua opsi pengaturan",
|
SubTitle: "Semua Pengaturan",
|
||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "Atur Ulang Semua Pengaturan",
|
Title: "Setel Ulang Semua Pengaturan",
|
||||||
SubTitle: "Atur ulang semua opsi pengaturan ke nilai default",
|
SubTitle: "Mengembalikan semua pengaturan ke nilai default",
|
||||||
Action: "Atur Ulang Sekarang",
|
Action: "Setel Ulang",
|
||||||
Confirm: "Konfirmasi untuk mengatur ulang semua pengaturan?",
|
Confirm:
|
||||||
|
"Anda yakin ingin mengembalikan semua pengaturan ke nilai default?",
|
||||||
},
|
},
|
||||||
Clear: {
|
Clear: {
|
||||||
Title: "Hapus Semua Data",
|
Title: "Hapus Semua Data",
|
||||||
SubTitle: "Hapus semua data obrolan dan pengaturan",
|
SubTitle: "Semua data yang tersimpan secara lokal akan dihapus",
|
||||||
Action: "Hapus Sekarang",
|
Action: "Hapus",
|
||||||
Confirm:
|
Confirm:
|
||||||
"Konfirmasi untuk menghapus semua data obrolan dan pengaturan?",
|
"Apakah Anda yakin ingin menghapus semua data yang tersimpan secara lokal?",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // PERHATIAN: jika Anda ingin menambahkan terjemahan baru, harap jangan terjemahkan nilai ini, biarkan sebagai `Language`
|
Name: "Bahasa", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Semua Bahasa",
|
All: "Semua Bahasa",
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Ukuran Font",
|
Title: "Ukuran Font",
|
||||||
SubTitle: "Ukuran font untuk konten obrolan",
|
SubTitle: "Ubah ukuran font konten chat",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Font Obrolan",
|
|
||||||
SubTitle:
|
|
||||||
"Font dari konten obrolan, biarkan kosong untuk menerapkan font default global",
|
|
||||||
Placeholder: "Nama Font",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Suntikkan Pesan Sistem",
|
Title: "Suntikkan Petunjuk Sistem",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Memaksa menambahkan pesan sistem simulasi ChatGPT di awal daftar pesan setiap permintaan",
|
"Tambahkan petunjuk simulasi sistem ChatGPT di awal daftar pesan yang diminta dalam setiap permintaan",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "Pra-pemrosesan Input Pengguna",
|
Title: "Template Input",
|
||||||
SubTitle: "Pesan terbaru pengguna akan diisi ke template ini",
|
SubTitle: "Pesan baru akan diisi menggunakan template ini",
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Versi Saat Ini: ${x}`,
|
Version: (x: string) => `Version: ${x}`,
|
||||||
IsLatest: "Sudah versi terbaru",
|
IsLatest: "Versi terbaru",
|
||||||
CheckUpdate: "Periksa Pembaruan",
|
CheckUpdate: "Periksa Pembaruan",
|
||||||
IsChecking: "Sedang memeriksa pembaruan...",
|
IsChecking: "Memeriksa pembaruan...",
|
||||||
FoundUpdate: (x: string) => `Versi Baru Ditemukan: ${x}`,
|
FoundUpdate: (x: string) => `Versi terbaru ditemukan: ${x}`,
|
||||||
GoToUpdate: "Pergi ke Pembaruan",
|
GoToUpdate: "Perbarui Sekarang",
|
||||||
},
|
|
||||||
SendKey: "Kunci Kirim",
|
|
||||||
Theme: "Tema",
|
|
||||||
TightBorder: "Mode Tanpa Border",
|
|
||||||
SendPreviewBubble: {
|
|
||||||
Title: "Preview Bubble",
|
|
||||||
SubTitle: "Pratinjau konten Markdown di bubble pratinjau",
|
|
||||||
},
|
},
|
||||||
AutoGenerateTitle: {
|
AutoGenerateTitle: {
|
||||||
Title: "Otomatis Membuat Judul",
|
Title: "Hasilkan Judul Otomatis",
|
||||||
SubTitle: "Membuat judul yang sesuai berdasarkan konten obrolan",
|
SubTitle: "Hasilkan judul yang sesuai berdasarkan konten percakapan",
|
||||||
},
|
},
|
||||||
Sync: {
|
Sync: {
|
||||||
CloudState: "Data Cloud",
|
CloudState: "Pembaruan Terakhir",
|
||||||
NotSyncYet: "Belum disinkronkan",
|
NotSyncYet: "Belum disinkronkan",
|
||||||
Success: "Sinkronisasi Berhasil",
|
Success: "Sinkronisasi Berhasil",
|
||||||
Fail: "Sinkronisasi Gagal",
|
Fail: "Sinkronisasi Gagal",
|
||||||
|
|
||||||
Config: {
|
Config: {
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Konfigurasi Sinkronisasi Cloud",
|
Title: "Konfigurasi Sinkronisasi",
|
||||||
Check: "Periksa Ketersediaan",
|
|
||||||
},
|
},
|
||||||
SyncType: {
|
SyncType: {
|
||||||
Title: "Jenis Sinkronisasi",
|
Title: "Tipe Sinkronisasi",
|
||||||
SubTitle: "Pilih server sinkronisasi favorit",
|
SubTitle: "Pilih layanan sinkronisasi favorit Anda",
|
||||||
},
|
},
|
||||||
Proxy: {
|
Proxy: {
|
||||||
Title: "Aktifkan Proxy",
|
Title: "Aktifkan Proxy CORS",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Saat menyinkronkan di browser, proxy harus diaktifkan untuk menghindari pembatasan lintas domain",
|
"Aktifkan Proxy untuk menghindari pembatasan atau pemblokiran lintas sumber",
|
||||||
},
|
},
|
||||||
ProxyUrl: {
|
ProxyUrl: {
|
||||||
Title: "Alamat Proxy",
|
Title: "Lokasi Titik Akhir Proxy CORS",
|
||||||
SubTitle: "Hanya berlaku untuk proxy lintas domain bawaan proyek ini",
|
SubTitle: "Hanya berlaku untuk Proxy CORS bawaan untuk proyek ini",
|
||||||
},
|
},
|
||||||
|
|
||||||
WebDav: {
|
WebDav: {
|
||||||
Endpoint: "Alamat WebDAV",
|
Endpoint: "Lokasi Titik Akhir WebDAV",
|
||||||
UserName: "Nama Pengguna",
|
UserName: "User Pengguna",
|
||||||
Password: "Kata Sandi",
|
Password: "Kata Sandi",
|
||||||
},
|
},
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "Url REST Redis UpStash",
|
|
||||||
UserName: "Nama Cadangan",
|
|
||||||
Password: "Token REST Redis UpStash",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
LocalState: "Data Lokal",
|
SendKey: "Kirim",
|
||||||
Overview: (overview: any) => {
|
Theme: "Tema",
|
||||||
return `${overview.chat} percakapan, ${overview.message} pesan, ${overview.prompt} prompt, ${overview.mask} masker`;
|
TightBorder: "Batas Ketat",
|
||||||
},
|
SendPreviewBubble: {
|
||||||
ImportFailed: "Impor Gagal",
|
Title: "Pratinjau Obrolan",
|
||||||
|
SubTitle: "Pratinjau Obrolan dengan markdown",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Halaman Awal Masker",
|
Title: "Layar Pembuka Masks",
|
||||||
SubTitle: "Tampilkan halaman awal masker saat memulai obrolan baru",
|
SubTitle:
|
||||||
|
"Tampilkan layar pembuka masks sebelum memulai percakapan baru",
|
||||||
},
|
},
|
||||||
Builtin: {
|
Builtin: {
|
||||||
Title: "Sembunyikan Masker Bawaan",
|
Title: "Sembunyikan Masks Bawaan",
|
||||||
SubTitle: "Sembunyikan masker bawaan dari semua daftar masker",
|
SubTitle: "Sembunyikan Masks bawaan dari daftar masks",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Nonaktifkan Pelengkapan Prompt Otomatis",
|
Title: "Nonaktifkan Otomatisasi",
|
||||||
SubTitle:
|
SubTitle: "Aktifkan/Matikan otomatisasi",
|
||||||
"Ketik / di awal kotak input untuk memicu pelengkapan otomatis",
|
|
||||||
},
|
},
|
||||||
List: "Daftar Prompt Kustom",
|
List: "Daftar Prompt",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Bawaan ${builtin} item, pengguna ${custom} item`,
|
`${builtin} bawaan, ${custom} penggunaan khusus`,
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Daftar Prompt",
|
Title: "Daftar Prompt",
|
||||||
Add: "Baru",
|
Add: "Tambahkan",
|
||||||
Search: "Cari Prompt",
|
Search: "Cari Prompt",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
@@ -263,293 +230,155 @@ const id: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Jumlah Pesan Sejarah",
|
Title: "Jumlah Pesan Riwayat",
|
||||||
SubTitle: "Jumlah pesan sejarah yang dibawa setiap permintaan",
|
SubTitle: "Jumlah pesan yang akan dikirim setiap permintaan",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Ambang Batas Kompresi Pesan Sejarah",
|
Title: "Batas Kompresi Riwayat",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Ketika pesan sejarah yang tidak terkompresi melebihi nilai ini, akan dikompresi",
|
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Cek Saldo",
|
Title: "Saldo Akun",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Digunakan bulan ini $${used}, total langganan $${total}`;
|
return `Digunakan bulan ini: ${used}, total langganan: ${total}`;
|
||||||
},
|
|
||||||
IsChecking: "Sedang memeriksa…",
|
|
||||||
Check: "Periksa Lagi",
|
|
||||||
NoAccess: "Masukkan API Key atau kata sandi akses untuk melihat saldo",
|
|
||||||
},
|
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "Kata Sandi Akses",
|
|
||||||
SubTitle: "Administrator telah mengaktifkan akses terenkripsi",
|
|
||||||
Placeholder: "Masukkan kata sandi akses",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Antarmuka Kustom",
|
|
||||||
SubTitle: "Apakah akan menggunakan layanan Azure atau OpenAI kustom",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Penyedia Layanan Model",
|
|
||||||
SubTitle: "Ganti penyedia layanan yang berbeda",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Gunakan OpenAI Key kustom untuk menghindari batasan akses kata sandi",
|
|
||||||
Placeholder: "OpenAI API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Selain alamat default, harus menyertakan http(s)://",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Kunci Antarmuka",
|
|
||||||
SubTitle:
|
|
||||||
"Gunakan Azure Key kustom untuk menghindari batasan akses kata sandi",
|
|
||||||
Placeholder: "Azure API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Contoh:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versi Antarmuka (azure api version)",
|
|
||||||
SubTitle: "Pilih versi parsial tertentu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Kunci Antarmuka",
|
|
||||||
SubTitle:
|
|
||||||
"Gunakan Anthropic Key kustom untuk menghindari batasan akses kata sandi",
|
|
||||||
Placeholder: "Anthropic API Key",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Contoh:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versi Antarmuka (claude api version)",
|
|
||||||
SubTitle: "Pilih versi API tertentu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Kunci API",
|
|
||||||
SubTitle: "Dapatkan kunci API Anda dari Google AI",
|
|
||||||
Placeholder: "Masukkan kunci API Studio Google AI Anda",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Akhir",
|
|
||||||
SubTitle: "Contoh:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Versi API (hanya untuk gemini-pro)",
|
|
||||||
SubTitle: "Pilih versi API tertentu",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Tingkat Filter Keamanan Google",
|
|
||||||
SubTitle: "Atur tingkat filter konten",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Gunakan Baidu API Key kustom",
|
|
||||||
Placeholder: "Baidu API Key",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "Gunakan Baidu Secret Key kustom",
|
|
||||||
Placeholder: "Baidu Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Tidak mendukung kustom, pergi ke .env untuk konfigurasi",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Kunci Antarmuka",
|
|
||||||
SubTitle: "Gunakan ByteDance API Key kustom",
|
|
||||||
Placeholder: "ByteDance API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Contoh:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Kunci Antarmuka",
|
|
||||||
SubTitle: "Gunakan Alibaba Cloud API Key kustom",
|
|
||||||
Placeholder: "Alibaba Cloud API Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Alamat Antarmuka",
|
|
||||||
SubTitle: "Contoh:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Nama Model Kustom",
|
|
||||||
SubTitle: "Tambahkan opsi model kustom, pisahkan dengan koma",
|
|
||||||
},
|
},
|
||||||
|
IsChecking: "Memeriksa...",
|
||||||
|
Check: "Periksa",
|
||||||
|
NoAccess: "Masukkan kunci API untuk memeriksa saldo",
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Randomness (temperature)",
|
Title: "Suhu",
|
||||||
SubTitle: "Semakin tinggi nilainya, semakin acak responsnya",
|
SubTitle: "Semakin tinggi nilainya, semakin acak keluarannya",
|
||||||
},
|
},
|
||||||
TopP: {
|
TopP: {
|
||||||
Title: "Sampling Inti (top_p)",
|
Title: "Top P",
|
||||||
SubTitle:
|
SubTitle: "Tidak mengubah nilai dengan suhu",
|
||||||
"Mirip dengan randomness, tetapi jangan ubah bersama randomness",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Batas Token Per Respons",
|
Title: "Token Maksimum",
|
||||||
SubTitle: "Jumlah token maksimum yang digunakan per interaksi",
|
SubTitle: "Panjang maksimum token input dan output",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Kedekatan Topik (presence_penalty)",
|
Title: "Penalti Kehadiran",
|
||||||
SubTitle:
|
SubTitle: "Semakin tinggi nilai, semakin mungkin topik baru muncul",
|
||||||
"Semakin tinggi nilainya, semakin besar kemungkinan memperluas ke topik baru",
|
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Hukuman Frekuensi (frequency_penalty)",
|
Title: "Penalti Frekuensi",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Semakin tinggi nilainya, semakin besar kemungkinan mengurangi kata-kata yang berulang",
|
"Semakin tinggi nilai, semakin rendah kemungkinan penggunaan ulang baris yang sama",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Obrolan Baru",
|
DefaultTopic: "Percakapan Baru",
|
||||||
BotHello: "Ada yang bisa saya bantu?",
|
BotHello: "Halo! Bagaimana saya bisa membantu Anda hari ini?",
|
||||||
Error: "Terjadi kesalahan, coba lagi nanti",
|
Error: "Terjadi kesalahan, silakan coba lagi nanti.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Ini adalah ringkasan obrolan sebelumnya sebagai latar belakang: " +
|
"Ini adalah ringkasan singkat dari riwayat percakapan: " + content,
|
||||||
content,
|
|
||||||
Topic:
|
Topic:
|
||||||
"Gunakan empat hingga lima kata untuk langsung memberikan ringkasan topik kalimat ini, tanpa penjelasan, tanpa tanda baca, tanpa kata pengisi, tanpa teks tambahan, tanpa menebalkan. Jika tidak ada topik, langsung jawab 'Obrolan Santai'",
|
"Buat judul berisi empat hingga lima kata untuk percakapan kita yang tidak akan disertakan dalam ringkasan percakapan, seperti instruksi, format, kutipan, tanda baca awal, tanda kutip pendahuluan, atau karakter tambahan. Silakan coba dengan kutipan berakhir.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Berikan ringkasan singkat tentang konten obrolan, untuk digunakan sebagai prompt konteks selanjutnya, dalam 200 kata atau kurang",
|
"Buat ringkasan percakapan dalam 200 kata yang akan digunakan sebagai promp di masa depan.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Telah disalin ke clipboard",
|
Success: "Tersalin ke clipboard",
|
||||||
Failed: "Gagal menyalin, mohon berikan izin clipboard",
|
Failed:
|
||||||
|
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
|
||||||
},
|
},
|
||||||
Download: {
|
Download: {
|
||||||
Success: "Konten telah diunduh ke direktori Anda.",
|
Success: "Konten berhasil diunduh ke direktori Anda.",
|
||||||
Failed: "Unduhan gagal.",
|
Failed: "Unduhan gagal.",
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Berisi ${x} prompt preset`,
|
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
|
||||||
Edit: "Pengaturan Obrolan Saat Ini",
|
Edit: "Pengaturan Obrolan Saat Ini",
|
||||||
Add: "Tambah Obrolan",
|
Add: "Tambahkan Promp",
|
||||||
Clear: "Konteks telah dihapus",
|
Clear: "Bersihkan Konteks",
|
||||||
Revert: "Kembalikan Konteks",
|
Revert: "Kembali ke Posisi Sebelumnya",
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Anda adalah seorang asisten",
|
Sysmessage: "Anda adalah asisten yang",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Masker",
|
Name: "Masks",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Preset Karakter Masker",
|
Title: "Template Promp",
|
||||||
SubTitle: (count: number) => `${count} definisi karakter preset`,
|
SubTitle: (count: number) => `${count} template prompt`,
|
||||||
Search: "Cari Masker Karakter",
|
Search: "Cari template",
|
||||||
Create: "Buat Baru",
|
Create: "Buat",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Berisi ${count} obrolan preset`,
|
Info: (count: number) => `${count} prompt`,
|
||||||
Chat: "Obrolan",
|
Chat: "Obrolan",
|
||||||
View: "Lihat",
|
View: "Lihat",
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
Delete: "Hapus",
|
Delete: "Hapus",
|
||||||
DeleteConfirm: "Konfirmasi penghapusan?",
|
DeleteConfirm: "Anda yakin ingin menghapus?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Edit Masker Preset ${readonly ? "(Hanya Baca)" : ""}`,
|
`Edit Template Prompt ${readonly ? "(hanya baca)" : ""}`,
|
||||||
Download: "Unduh Preset",
|
Download: "Unduh",
|
||||||
Clone: "Klon Preset",
|
Clone: "Duplikat",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Avatar Karakter",
|
Avatar: "Avatar Bot",
|
||||||
Name: "Nama Karakter",
|
Name: "Nama Bot",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "Gunakan Pengaturan Global",
|
Title: "Gunakan Konfigurasi Global",
|
||||||
SubTitle:
|
SubTitle: "Gunakan konfigurasi global dalam percakapan ini",
|
||||||
"Apakah obrolan saat ini akan menggunakan pengaturan model global?",
|
|
||||||
Confirm:
|
Confirm:
|
||||||
"Pengaturan kustom obrolan saat ini akan ditimpa secara otomatis, konfirmasi untuk mengaktifkan pengaturan global?",
|
"Pastikan untuk mengganti konfigurasi kustom dengan konfigurasi global?",
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "Sembunyikan Obrolan Preset",
|
Title: "Sembunyikan Prompt Konteks",
|
||||||
SubTitle:
|
SubTitle: "Tidak menampilkan prompt konteks dalam obrolan",
|
||||||
"Setelah disembunyikan, obrolan preset tidak akan muncul di antarmuka obrolan",
|
|
||||||
},
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "Bagikan Masker Ini",
|
Title: "Bagikan Masks Ini",
|
||||||
SubTitle: "Hasilkan tautan langsung ke masker ini",
|
SubTitle: "Buat tautan untuk masks ini",
|
||||||
Action: "Salin Tautan",
|
Action: "Salin Tautan",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Kembali",
|
Return: "Kembali",
|
||||||
Skip: "Mulai Sekarang",
|
Skip: "Lewati",
|
||||||
NotShow: "Jangan Tampilkan Lagi",
|
Title: "Pilih Masks",
|
||||||
|
SubTitle: "Berkonversasilah dengan diri Anda di balik masks",
|
||||||
|
More: "Lebih Lanjut",
|
||||||
|
NotShow: "Jangan Tampilkan Sekarang",
|
||||||
ConfirmNoShow:
|
ConfirmNoShow:
|
||||||
"Konfirmasi untuk menonaktifkan? Setelah dinonaktifkan, Anda dapat mengaktifkannya kembali kapan saja di pengaturan.",
|
"Pastikan untuk menonaktifkannya? Anda dapat mengaktifkannya nanti melalui pengaturan.",
|
||||||
Title: "Pilih Masker",
|
|
||||||
SubTitle: "Mulai sekarang, berinteraksi dengan pemikiran di balik masker",
|
|
||||||
More: "Lihat Semua",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Terdeteksi bahwa tautan sudah mengandung kode akses, apakah akan diisi secara otomatis?",
|
|
||||||
Settings:
|
|
||||||
"Terdeteksi bahwa tautan mengandung pengaturan preset, apakah akan diisi secara otomatis?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Konfirmasi",
|
Confirm: "Konfirmasi",
|
||||||
Cancel: "Batal",
|
Cancel: "Batal",
|
||||||
Close: "Tutup",
|
Close: "Tutup",
|
||||||
Create: "Buat Baru",
|
Create: "Buat",
|
||||||
Edit: "Edit",
|
Edit: "Edit",
|
||||||
Export: "Ekspor",
|
|
||||||
Import: "Impor",
|
|
||||||
Sync: "Sinkronkan",
|
|
||||||
Config: "Konfigurasi",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
Description: {
|
||||||
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan",
|
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan"
|
||||||
},
|
},
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Messages: "Pesan",
|
Messages: "Pesan",
|
||||||
Topic: "Topik",
|
Topic: "Topik",
|
||||||
Time: "Waktu",
|
Time: "Tanggal & Waktu",
|
||||||
|
},
|
||||||
|
URLCommand: {
|
||||||
|
Code: "Kode akses terdeteksi dari url, konfirmasi untuk mendaftar ? ",
|
||||||
|
Settings: "Pengaturan terdeteksi dari url, konfirmasi untuk diterapkan ?",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -97,17 +97,7 @@ function setItem(key: string, value: string) {
|
|||||||
|
|
||||||
function getLanguage() {
|
function getLanguage() {
|
||||||
try {
|
try {
|
||||||
const locale = new Intl.Locale(navigator.language).maximize();
|
return navigator.language.toLowerCase();
|
||||||
const region = locale?.region?.toLowerCase();
|
|
||||||
// 1. check region code in ALL_LANGS
|
|
||||||
if (AllLangs.includes(region as Lang)) {
|
|
||||||
return region as Lang;
|
|
||||||
}
|
|
||||||
// 2. check language code in ALL_LANGS
|
|
||||||
if (AllLangs.includes(locale.language as Lang)) {
|
|
||||||
return locale.language as Lang;
|
|
||||||
}
|
|
||||||
return DEFAULT_LANG;
|
|
||||||
} catch {
|
} catch {
|
||||||
return DEFAULT_LANG;
|
return DEFAULT_LANG;
|
||||||
}
|
}
|
||||||
@@ -120,7 +110,15 @@ export function getLang(): Lang {
|
|||||||
return savedLang as Lang;
|
return savedLang as Lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getLanguage();
|
const lang = getLanguage();
|
||||||
|
|
||||||
|
for (const option of AllLangs) {
|
||||||
|
if (lang.includes(option)) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_LANG;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeLang(lang: Lang) {
|
export function changeLang(lang: Lang) {
|
||||||
|
|||||||
@@ -1,566 +1,238 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const it: PartialLocaleType = {
|
const it: PartialLocaleType = {
|
||||||
WIP: "Work in progress...",
|
WIP: "Work in progress...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "API Key non valido rilevato. Vai alla pagina [Impostazioni](/#/settings) per controllare se l'API Key è configurata correttamente."
|
"Accesso non autorizzato, inserire il codice di accesso nella [pagina](/#/auth) delle impostazioni.",
|
||||||
: "La password di accesso è errata o mancante. Vai alla pagina [Login](/#/auth) per inserire la password corretta o inserisci la tua API Key OpenAI nella pagina [Impostazioni](/#/settings).",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Password richiesta",
|
|
||||||
Tips: "L'amministratore ha abilitato la verifica della password. Inserisci il codice di accesso qui sotto",
|
|
||||||
SubTips: "O inserisci la tua chiave API OpenAI o Google",
|
|
||||||
Input: "Inserisci il codice di accesso qui",
|
|
||||||
Confirm: "Conferma",
|
|
||||||
Later: "Più tardi",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} conversazioni`,
|
ChatItemCount: (count: number) => `${count} messaggi`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Totale ${count} conversazioni`,
|
SubTitle: (count: number) => `${count} messaggi con ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Modifica cronologia messaggi",
|
|
||||||
Topic: {
|
|
||||||
Title: "Argomento della chat",
|
|
||||||
SubTitle: "Modifica l'argomento della chat corrente",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Visualizza l'elenco dei messaggi",
|
ChatList: "Vai alla Chat List",
|
||||||
CompressedHistory: "Visualizza la cronologia Prompt compressa",
|
CompressedHistory: "Prompt di memoria della cronologia compressa",
|
||||||
Export: "Esporta la cronologia chat",
|
Export: "Esportazione di tutti i messaggi come Markdown",
|
||||||
Copy: "Copia",
|
Copy: "Copia",
|
||||||
Stop: "Interrompi",
|
Stop: "Stop",
|
||||||
Retry: "Riprova",
|
Retry: "Riprova",
|
||||||
Pin: "Fissa",
|
Delete: "Delete",
|
||||||
PinToastContent: "1 conversazione fissata ai suggerimenti predefiniti",
|
|
||||||
PinToastAction: "Visualizza",
|
|
||||||
Delete: "Elimina",
|
|
||||||
Edit: "Modifica",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Rinomina Chat",
|
||||||
new: "Nuova chat",
|
Typing: "Typing…",
|
||||||
newm: "Nuova chat da maschera",
|
|
||||||
next: "Chat successiva",
|
|
||||||
prev: "Chat precedente",
|
|
||||||
clear: "Pulisci contesto",
|
|
||||||
del: "Elimina chat",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Interrompi risposta",
|
|
||||||
ToBottom: "Scorri fino al più recente",
|
|
||||||
Theme: {
|
|
||||||
auto: "Tema automatico",
|
|
||||||
light: "Tema chiaro",
|
|
||||||
dark: "Tema scuro",
|
|
||||||
},
|
|
||||||
Prompt: "Comandi rapidi",
|
|
||||||
Masks: "Tutte le maschere",
|
|
||||||
Clear: "Pulisci chat",
|
|
||||||
Settings: "Impostazioni conversazione",
|
|
||||||
UploadImage: "Carica immagine",
|
|
||||||
},
|
|
||||||
Rename: "Rinomina conversazione",
|
|
||||||
Typing: "Digitazione in corso…",
|
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} per inviare`;
|
var inputHints = `Scrivi qualcosa e premi ${submitKey} per inviare`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter per andare a capo";
|
inputHints += ", premi Shift + Enter per andare a capo";
|
||||||
}
|
}
|
||||||
return (
|
return inputHints;
|
||||||
inputHints +
|
|
||||||
",/ per attivare il completamento automatico, : per attivare il comando"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
Send: "Invia",
|
Send: "Invia",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Pulisci memoria",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Salva come maschera",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
IsContext: "Suggerimenti predefiniti",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Condividi cronologia chat",
|
Title: "Tutti i messaggi",
|
||||||
Copy: "Copia tutto",
|
Copy: "Copia tutto",
|
||||||
Download: "Scarica file",
|
Download: "Scarica",
|
||||||
Share: "Condividi su ShareGPT",
|
MessageFromYou: "Messaggio da te",
|
||||||
MessageFromYou: "Utente",
|
MessageFromChatGPT: "Messaggio da ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Formato di esportazione",
|
|
||||||
SubTitle: "Puoi esportare come testo Markdown o immagine PNG",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Includi contesto maschera",
|
|
||||||
SubTitle: "Mostrare il contesto della maschera nei messaggi",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Seleziona",
|
|
||||||
Preview: "Anteprima",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Generazione dello screenshot in corso",
|
|
||||||
Modal:
|
|
||||||
"Tieni premuto o fai clic con il tasto destro per salvare l'immagine",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Cerca messaggi",
|
|
||||||
All: "Seleziona tutto",
|
|
||||||
Latest: "Ultimi messaggi",
|
|
||||||
Clear: "Pulisci selezione",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Riassunto storico",
|
Title: "Prompt di memoria",
|
||||||
EmptyContent:
|
EmptyContent: "Vuoto.",
|
||||||
"Il contenuto della conversazione è troppo breve, nessun riassunto necessario",
|
Copy: "Copia tutto",
|
||||||
Send: "Comprimi automaticamente la cronologia chat e inviala come contesto",
|
Send: "Send Memory",
|
||||||
Copy: "Copia riassunto",
|
Reset: "Reset Session",
|
||||||
Reset: "[unused]",
|
ResetConfirm:
|
||||||
ResetConfirm: "Confermi la cancellazione del riassunto storico?",
|
"Ripristinare cancellerà la conversazione corrente e la cronologia di memoria. Sei sicuro che vuoi riavviare?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Nuova chat",
|
NewChat: "Nuova Chat",
|
||||||
DeleteChat: "Confermi l'eliminazione della conversazione selezionata?",
|
DeleteChat: "Confermare la cancellazione della conversazione selezionata?",
|
||||||
DeleteToast: "Conversazione eliminata",
|
DeleteToast: "Chat Cancellata",
|
||||||
Revert: "Annulla",
|
Revert: "Revert",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Impostazioni",
|
Title: "Impostazioni",
|
||||||
SubTitle: "Tutte le opzioni di impostazione",
|
SubTitle: "Tutte le impostazioni",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Ripristina tutte le impostazioni",
|
|
||||||
SubTitle: "Ripristina tutte le opzioni ai valori predefiniti",
|
|
||||||
Action: "Ripristina subito",
|
|
||||||
Confirm: "Confermi il ripristino di tutte le impostazioni?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Elimina tutti i dati",
|
|
||||||
SubTitle: "Elimina tutte le chat e i dati delle impostazioni",
|
|
||||||
Action: "Elimina subito",
|
|
||||||
Confirm:
|
|
||||||
"Confermi l'eliminazione di tutte le chat e dei dati delle impostazioni?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENZIONE: se vuoi aggiungere una nuova traduzione, non tradurre questo valore, lascialo come `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Tutte le lingue",
|
All: "Tutte le lingue",
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Dimensione del carattere",
|
Title: "Dimensione carattere",
|
||||||
SubTitle: "Dimensione del carattere per il contenuto della chat",
|
SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Font della Chat",
|
|
||||||
SubTitle:
|
|
||||||
"Carattere del contenuto della chat, lascia vuoto per applicare il carattere predefinito globale",
|
|
||||||
Placeholder: "Nome del Font",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Inserisci suggerimenti di sistema",
|
Title: "Inserisci Prompts di Sistema",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Aggiungi forzatamente un suggerimento di sistema simulato di ChatGPT all'inizio della lista dei messaggi per ogni richiesta",
|
"Aggiungi forzatamente un prompt di sistema simulato di ChatGPT all'inizio della lista dei messaggi per ogni richiesta",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Preprocessing dell'input utente",
|
|
||||||
SubTitle:
|
|
||||||
"L'ultimo messaggio dell'utente verrà inserito in questo modello",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Versione attuale: ${x}`,
|
Version: (x: string) => `Versione: ${x}`,
|
||||||
IsLatest: "È l'ultima versione",
|
IsLatest: "Ultima versione",
|
||||||
CheckUpdate: "Controlla aggiornamenti",
|
CheckUpdate: "Controlla aggiornamenti",
|
||||||
IsChecking: "Verifica aggiornamenti in corso...",
|
IsChecking: "Sto controllando gli aggiornamenti...",
|
||||||
FoundUpdate: (x: string) => `Nuova versione trovata: ${x}`,
|
FoundUpdate: (x: string) => `Trovata nuova versione: ${x}`,
|
||||||
GoToUpdate: "Vai all'aggiornamento",
|
GoToUpdate: "Aggiorna",
|
||||||
},
|
},
|
||||||
SendKey: "Tasto di invio",
|
SendKey: "Tasto invia",
|
||||||
Theme: "Tema",
|
Theme: "Tema",
|
||||||
TightBorder: "Modalità senza bordi",
|
TightBorder: "Schermo intero",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Bolla di anteprima",
|
Title: "Anteprima di digitazione",
|
||||||
SubTitle: "Anteprima del contenuto Markdown nella bolla di anteprima",
|
SubTitle: "Preview markdown in bubble",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Generazione automatica del titolo",
|
|
||||||
SubTitle:
|
|
||||||
"Genera un titolo appropriato in base al contenuto della conversazione",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Dati cloud",
|
|
||||||
NotSyncYet: "Non è ancora avvenuta alcuna sincronizzazione",
|
|
||||||
Success: "Sincronizzazione riuscita",
|
|
||||||
Fail: "Sincronizzazione fallita",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Configura sincronizzazione cloud",
|
|
||||||
Check: "Controlla disponibilità",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Tipo di sincronizzazione",
|
|
||||||
SubTitle: "Scegli il server di sincronizzazione preferito",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Abilita proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Durante la sincronizzazione nel browser, è necessario abilitare il proxy per evitare restrizioni CORS",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Indirizzo proxy",
|
|
||||||
SubTitle: "Solo per il proxy CORS fornito con questo progetto",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "Indirizzo WebDAV",
|
|
||||||
UserName: "Nome utente",
|
|
||||||
Password: "Password",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "URL REST di UpStash Redis",
|
|
||||||
UserName: "Nome di backup",
|
|
||||||
Password: "Token REST di UpStash Redis",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Dati locali",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} chat, ${overview.message} messaggi, ${overview.prompt} suggerimenti, ${overview.mask} maschere`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Importazione fallita",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Pagina di avvio delle maschere",
|
Title: "Mask Splash Screen",
|
||||||
SubTitle:
|
SubTitle: "Show a mask splash screen before starting new chat",
|
||||||
"Mostra la pagina di avvio delle maschere quando si avvia una nuova chat",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Nascondi maschere predefinite",
|
|
||||||
SubTitle:
|
|
||||||
"Nascondi le maschere predefinite in tutte le liste delle maschere",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Disabilita completamento automatico dei suggerimenti",
|
Title: "Disabilita l'auto completamento",
|
||||||
SubTitle:
|
SubTitle: "Input / per attivare il completamento automatico",
|
||||||
"Inserisci / all'inizio della casella di input per attivare il completamento automatico",
|
|
||||||
},
|
},
|
||||||
List: "Elenco dei suggerimenti personalizzati",
|
List: "Elenco dei suggerimenti",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`${builtin} predefiniti, ${custom} definiti dall'utente`,
|
`${builtin} built-in, ${custom} user-defined`,
|
||||||
Edit: "Modifica",
|
Edit: "Modifica",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Elenco dei suggerimenti",
|
Title: "Prompt List",
|
||||||
Add: "Nuovo",
|
Add: "Add One",
|
||||||
Search: "Cerca suggerimenti",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Modifica suggerimenti",
|
Title: "Edit Prompt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Numero di messaggi storici inclusi",
|
Title: "Conteggio dei messaggi allegati",
|
||||||
SubTitle: "Numero di messaggi storici inclusi in ogni richiesta",
|
SubTitle: "Numero di messaggi inviati allegati per richiesta",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Soglia di compressione dei messaggi storici",
|
Title: "Soglia di compressione della cronologia",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Quando i messaggi storici non compressi superano questo valore, verranno compressi",
|
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Verifica saldo",
|
Title: "Bilancio Account",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Utilizzato questo mese $${used}, totale abbonamento $${total}`;
|
return `Attualmente usato in questo mese $${used}, soglia massima $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Verifica in corso…",
|
IsChecking: "Controllando...",
|
||||||
Check: "Verifica di nuovo",
|
Check: "Controlla ancora",
|
||||||
NoAccess:
|
NoAccess: "Inserire la chiave API per controllare il saldo",
|
||||||
"Inserisci API Key o password di accesso per visualizzare il saldo",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Modello GPT",
|
||||||
AccessCode: {
|
|
||||||
Title: "Password di accesso",
|
|
||||||
SubTitle: "L'amministratore ha abilitato l'accesso criptato",
|
|
||||||
Placeholder: "Inserisci la password di accesso",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Interfaccia personalizzata",
|
|
||||||
SubTitle: "Utilizzare servizi Azure o OpenAI personalizzati",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Fornitore del modello",
|
|
||||||
SubTitle: "Cambia fornitore di servizi",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Utilizza una chiave OpenAI personalizzata per bypassare le limitazioni di accesso",
|
|
||||||
Placeholder: "API Key OpenAI",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Deve includere http(s):// oltre all'indirizzo predefinito",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Chiave dell'interfaccia",
|
|
||||||
SubTitle:
|
|
||||||
"Utilizza una chiave Azure personalizzata per bypassare le limitazioni di accesso",
|
|
||||||
Placeholder: "Chiave API Azure",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Esempio:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versione dell'interfaccia (versione api azure)",
|
|
||||||
SubTitle: "Scegli una versione specifica",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Chiave dell'interfaccia",
|
|
||||||
SubTitle:
|
|
||||||
"Utilizza una chiave Anthropic personalizzata per bypassare le limitazioni di accesso",
|
|
||||||
Placeholder: "API Key Anthropic",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Esempio:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Versione dell'interfaccia (versione api claude)",
|
|
||||||
SubTitle: "Scegli una versione API specifica",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Ottieni la tua chiave API da Google AI",
|
|
||||||
Placeholder: "Inserisci la tua chiave API Google AI Studio",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Esempio:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Versione API (solo per gemini-pro)",
|
|
||||||
SubTitle: "Scegli una versione API specifica",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Livello di filtraggio sicurezza Google",
|
|
||||||
SubTitle: "Imposta il livello di filtraggio dei contenuti",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Utilizza una chiave API Baidu personalizzata",
|
|
||||||
Placeholder: "API Key Baidu",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "Utilizza una chiave segreta Baidu personalizzata",
|
|
||||||
Placeholder: "Secret Key Baidu",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle:
|
|
||||||
"Non supporta configurazioni personalizzate, andare su .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Chiave dell'interfaccia",
|
|
||||||
SubTitle: "Utilizza una chiave API ByteDance personalizzata",
|
|
||||||
Placeholder: "API Key ByteDance",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Esempio:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Chiave dell'interfaccia",
|
|
||||||
SubTitle: "Utilizza una chiave API Alibaba Cloud personalizzata",
|
|
||||||
Placeholder: "API Key Alibaba Cloud",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Indirizzo dell'interfaccia",
|
|
||||||
SubTitle: "Esempio:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Nome del modello personalizzato",
|
|
||||||
SubTitle:
|
|
||||||
"Aggiungi opzioni di modelli personalizzati, separati da virgole",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Modello (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Casualità (temperature)",
|
Title: "Temperature",
|
||||||
SubTitle: "Valore più alto, risposte più casuali",
|
SubTitle: "Un valore maggiore rende l'output più casuale",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Campionamento nucleare (top_p)",
|
|
||||||
SubTitle:
|
|
||||||
"Simile alla casualità, ma non cambiarlo insieme alla casualità",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Limite di token per risposta (max_tokens)",
|
Title: "Token massimi",
|
||||||
SubTitle: "Numero massimo di token per ogni interazione",
|
SubTitle: "Lunghezza massima dei token in ingresso e dei token generati",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Novità del tema (presence_penalty)",
|
Title: "Penalità di presenza",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Valore più alto, maggiore possibilità di espandere a nuovi argomenti",
|
"Un valore maggiore aumenta la probabilità di parlare di nuovi argomenti",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Penalità di frequenza (frequency_penalty)",
|
Title: "Penalità di frequenza",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Valore più alto, maggiore possibilità di ridurre le ripetizioni",
|
"Un valore maggiore che diminuisce la probabilità di ripetere la stessa riga",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Nuova chat",
|
DefaultTopic: "Nuova conversazione",
|
||||||
BotHello: "Come posso aiutarti?",
|
BotHello: "Ciao, come posso aiutarti oggi?",
|
||||||
Error: "Si è verificato un errore, riprova più tardi",
|
Error: "Qualcosa è andato storto, riprova più tardi.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Questo è un riassunto della chat storica come contesto: " + content,
|
"Questo è un riassunto della cronologia delle chat tra l'IA e l'utente:" +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Riporta il tema di questa frase in modo conciso con quattro o cinque parole, senza spiegazioni, punteggiatura, interiezioni, testo superfluo e senza grassetto. Se non c'è un tema, rispondi direttamente con 'chit-chat'",
|
"Si prega di generare un titolo di quattro o cinque parole che riassuma la nostra conversazione senza alcuna traccia, punteggiatura, virgolette, punti, simboli o testo aggiuntivo. Rimuovere le virgolette",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Riassumi brevemente il contenuto della conversazione come prompt di contesto per il seguito, mantenendolo entro 200 parole",
|
"Riassumi brevemente la nostra discussione in 200 caratteri o meno per usarla come spunto per una futura conversazione.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Copiato negli appunti",
|
Success: "Copiato sugli appunti",
|
||||||
Failed: "Copia fallita, concedi i permessi per gli appunti",
|
Failed:
|
||||||
},
|
"Copia fallita, concedere l'autorizzazione all'accesso agli appunti",
|
||||||
Download: {
|
|
||||||
Success: "Contenuto scaricato nella tua directory.",
|
|
||||||
Failed: "Download fallito.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Include ${x} suggerimenti predefiniti`,
|
Toast: (x: any) => `Con ${x} prompts contestuali`,
|
||||||
Edit: "Impostazioni della conversazione attuale",
|
Edit: "Prompt contestuali e di memoria",
|
||||||
Add: "Aggiungi una conversazione",
|
Add: "Aggiungi altro",
|
||||||
Clear: "Contesto cancellato",
|
|
||||||
Revert: "Ripristina contesto",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Sei un assistente",
|
Sysmessage: "Sei un assistente che",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Maschera",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Maschere dei ruoli predefiniti",
|
Title: "Prompt Template",
|
||||||
SubTitle: (count: number) => `${count} definizioni di ruoli predefiniti`,
|
SubTitle: (count: number) => `${count} prompt templates`,
|
||||||
Search: "Cerca maschere di ruolo",
|
Search: "Search Templates",
|
||||||
Create: "Crea nuovo",
|
Create: "Create",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Include ${count} conversazioni predefinite`,
|
Info: (count: number) => `${count} prompts`,
|
||||||
Chat: "Conversazione",
|
Chat: "Chat",
|
||||||
View: "Visualizza",
|
View: "View",
|
||||||
Edit: "Modifica",
|
Edit: "Edit",
|
||||||
Delete: "Elimina",
|
Delete: "Delete",
|
||||||
DeleteConfirm: "Confermi eliminazione?",
|
DeleteConfirm: "Confirm to delete?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Modifica maschera predefinita ${readonly ? "(sola lettura)" : ""}`,
|
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
|
||||||
Download: "Scarica predefinito",
|
Download: "Download",
|
||||||
Clone: "Clona predefinito",
|
Clone: "Clone",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Avatar del ruolo",
|
Avatar: "Bot Avatar",
|
||||||
Name: "Nome del ruolo",
|
Name: "Bot Name",
|
||||||
Sync: {
|
|
||||||
Title: "Utilizza impostazioni globali",
|
|
||||||
SubTitle:
|
|
||||||
"La conversazione attuale utilizzerà le impostazioni globali del modello",
|
|
||||||
Confirm:
|
|
||||||
"Le impostazioni personalizzate della conversazione attuale verranno sovrascritte automaticamente, confermi l'attivazione delle impostazioni globali?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Nascondi conversazioni predefinite",
|
|
||||||
SubTitle:
|
|
||||||
"Le conversazioni predefinite non appariranno nella finestra della chat dopo averle nascoste",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Condividi questa maschera",
|
|
||||||
SubTitle: "Genera un link diretto a questa maschera",
|
|
||||||
Action: "Copia link",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Torna",
|
Return: "Return",
|
||||||
Skip: "Inizia subito",
|
Skip: "Skip",
|
||||||
NotShow: "Non mostrare più",
|
Title: "Pick a Mask",
|
||||||
ConfirmNoShow:
|
SubTitle: "Chat with the Soul behind the Mask",
|
||||||
"Confermi di disabilitare? Dopo la disabilitazione, puoi riattivare in qualsiasi momento dalle impostazioni.",
|
More: "Find More",
|
||||||
Title: "Scegli una maschera",
|
NotShow: "Not Show Again",
|
||||||
SubTitle: "Inizia ora e interagisci con il pensiero dietro la maschera",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
More: "Vedi tutto",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Codice di accesso rilevato nel link, riempirlo automaticamente?",
|
|
||||||
Settings:
|
|
||||||
"Impostazioni predefinite rilevate nel link, riempirle automaticamente?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Conferma",
|
Confirm: "Confirm",
|
||||||
Cancel: "Annulla",
|
Cancel: "Cancel",
|
||||||
Close: "Chiudi",
|
Close: "Close",
|
||||||
Create: "Crea",
|
Create: "Create",
|
||||||
Edit: "Modifica",
|
Edit: "Edit",
|
||||||
Export: "Esporta",
|
|
||||||
Import: "Importa",
|
|
||||||
Sync: "Sincronizza",
|
|
||||||
Config: "Configura",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title:
|
|
||||||
"Solo i messaggi dopo la cancellazione del contesto verranno visualizzati",
|
|
||||||
},
|
|
||||||
Model: "Modello",
|
Model: "Modello",
|
||||||
Messages: "Messaggi",
|
Messages: "Messaggi",
|
||||||
Topic: "Tema",
|
Topic: "Argomento",
|
||||||
Time: "Tempo",
|
Time: "Tempo",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,546 +1,301 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const jp: PartialLocaleType = {
|
const jp: PartialLocaleType = {
|
||||||
WIP: "この機能は開発中です",
|
WIP: "この機能は開発中です",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "無効なAPIキーが検出されました。[設定](/#/settings)ページでAPIキーが正しく設定されているか確認してください。"
|
"現在は未承認状態です。左下の設定ボタンをクリックし、アクセスパスワードかOpenAIのAPIキーを入力してください。",
|
||||||
: "アクセスパスワードが正しくないか空です。[ログイン](/#/auth)ページで正しいアクセスパスワードを入力するか、[設定](/#/settings)ページで自分のOpenAI APIキーを入力してください。",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "パスワードが必要です",
|
|
||||||
Tips: "管理者がパスワード認証を有効にしました。以下にアクセスコードを入力してください",
|
|
||||||
SubTips: "または、OpenAIまたはGoogle APIキーを入力してください",
|
|
||||||
Input: "ここにアクセスコードを入力",
|
|
||||||
Confirm: "確認",
|
|
||||||
Later: "後で",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count}件の会話`,
|
ChatItemCount: (count: number) => `${count} 通のチャット`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `合計${count}件の会話`,
|
SubTitle: (count: number) => `ChatGPTとの ${count} 通のチャット`,
|
||||||
EditMessage: {
|
EditMessage: {
|
||||||
Title: "メッセージ履歴を編集",
|
Title: "全てのメッセージを修正",
|
||||||
Topic: {
|
Topic: {
|
||||||
Title: "チャットテーマ",
|
Title: "トピック",
|
||||||
SubTitle: "現在のチャットテーマを変更",
|
SubTitle: "このトピックを変える",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "メッセージリストを見る",
|
ChatList: "メッセージリストを表示",
|
||||||
CompressedHistory: "圧縮された履歴プロンプトを見る",
|
CompressedHistory: "圧縮された履歴プロンプトを表示",
|
||||||
Export: "チャット履歴をエクスポート",
|
Export: "チャット履歴をエクスポート",
|
||||||
Copy: "コピー",
|
Copy: "コピー",
|
||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
Retry: "再試行",
|
Retry: "リトライ",
|
||||||
Pin: "固定",
|
Pin: "ピン",
|
||||||
PinToastContent: "1件の会話をプリセットプロンプトに固定しました",
|
PinToastContent:
|
||||||
PinToastAction: "見る",
|
"コンテキストプロンプトに1つのメッセージをピン留めしました",
|
||||||
|
PinToastAction: "表示",
|
||||||
Delete: "削除",
|
Delete: "削除",
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
},
|
},
|
||||||
Commands: {
|
|
||||||
new: "新しいチャット",
|
|
||||||
newm: "マスクから新しいチャット",
|
|
||||||
next: "次のチャット",
|
|
||||||
prev: "前のチャット",
|
|
||||||
clear: "コンテキストをクリア",
|
|
||||||
del: "チャットを削除",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "応答を停止",
|
|
||||||
ToBottom: "最新へスクロール",
|
|
||||||
Theme: {
|
|
||||||
auto: "自動テーマ",
|
|
||||||
light: "ライトモード",
|
|
||||||
dark: "ダークモード",
|
|
||||||
},
|
|
||||||
Prompt: "クイックコマンド",
|
|
||||||
Masks: "すべてのマスク",
|
|
||||||
Clear: "チャットをクリア",
|
|
||||||
Settings: "チャット設定",
|
|
||||||
UploadImage: "画像をアップロード",
|
|
||||||
},
|
|
||||||
Rename: "チャットの名前を変更",
|
Rename: "チャットの名前を変更",
|
||||||
Typing: "入力中…",
|
Typing: "入力中…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey}で送信`;
|
var inputHints = `${submitKey} で送信`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += "、Shift + Enterで改行";
|
inputHints += ",Shift + Enter で改行";
|
||||||
}
|
}
|
||||||
return inputHints + "、/で補完をトリガー、:でコマンドをトリガー";
|
return inputHints + ",/ で自動補完をトリガー";
|
||||||
},
|
},
|
||||||
Send: "送信",
|
Send: "送信",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "メモリをクリア",
|
Reset: "リセット",
|
||||||
SaveAs: "マスクとして保存",
|
SaveAs: "保存",
|
||||||
},
|
},
|
||||||
IsContext: "プリセットプロンプト",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "チャット履歴を共有",
|
Title: "チャット履歴をMarkdown形式でエクスポート",
|
||||||
Copy: "すべてコピー",
|
Copy: "すべてコピー",
|
||||||
Download: "ファイルをダウンロード",
|
Download: "ファイルをダウンロード",
|
||||||
Share: "ShareGPTに共有",
|
MessageFromYou: "あなたからのメッセージ",
|
||||||
MessageFromYou: "ユーザー",
|
MessageFromChatGPT: "ChatGPTからのメッセージ",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
Format: {
|
||||||
Title: "エクスポート形式",
|
Title: "フォーマットをエクスポート",
|
||||||
SubTitle: "MarkdownテキストまたはPNG画像としてエクスポートできます",
|
SubTitle: "マークダウン形式、PNG画像形式を選択できます。",
|
||||||
},
|
},
|
||||||
IncludeContext: {
|
IncludeContext: {
|
||||||
Title: "マスクコンテキストを含む",
|
Title: "コンテキストを含みますか?",
|
||||||
SubTitle: "メッセージにマスクコンテキストを表示するかどうか",
|
SubTitle: "コンテキストを含ませるか否か",
|
||||||
},
|
},
|
||||||
Steps: {
|
Steps: {
|
||||||
Select: "選択",
|
Select: "エクスポート設定",
|
||||||
Preview: "プレビュー",
|
Preview: "プレビュー",
|
||||||
},
|
},
|
||||||
Image: {
|
Image: {
|
||||||
Toast: "スクリーンショットを生成中",
|
Toast: "画像生成中...",
|
||||||
Modal: "長押しまたは右クリックして画像を保存",
|
Modal: "長押し、または右クリックで保存してください。",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "メッセージを検索",
|
Search: "検索",
|
||||||
All: "すべて選択",
|
All: "すべて選択",
|
||||||
Latest: "最新の数件",
|
Latest: "新しいメッセージを選択",
|
||||||
Clear: "選択をクリア",
|
Clear: "クリア",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "履歴の要約",
|
Title: "履歴メモリ",
|
||||||
EmptyContent: "対話内容が短いため、要約は不要です",
|
EmptyContent: "まだ記憶されていません",
|
||||||
Send: "チャット履歴を自動的に圧縮し、コンテキストとして送信",
|
Send: "メモリを送信",
|
||||||
Copy: "要約をコピー",
|
Copy: "メモリをコピー",
|
||||||
Reset: "[unused]",
|
Reset: "チャットをリセット",
|
||||||
ResetConfirm: "履歴の要約をリセットしてもよろしいですか?",
|
ResetConfirm:
|
||||||
|
"リセット後、現在のチャット履歴と過去のメモリがクリアされます。リセットしてもよろしいですか?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "新しいチャット",
|
NewChat: "新しいチャット",
|
||||||
DeleteChat: "選択した会話を削除してもよろしいですか?",
|
DeleteChat: "選択したチャットを削除してもよろしいですか?",
|
||||||
DeleteToast: "会話を削除しました",
|
DeleteToast: "チャットが削除されました",
|
||||||
Revert: "元に戻す",
|
Revert: "元に戻す",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "設定",
|
Title: "設定",
|
||||||
SubTitle: "すべての設定オプション",
|
SubTitle: "設定オプション",
|
||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "すべての設定をリセット",
|
Title: "設定をリセット",
|
||||||
SubTitle: "すべての設定項目をデフォルト値にリセット",
|
SubTitle: "すべての設定項目をデフォルトにリセットします",
|
||||||
Action: "今すぐリセット",
|
Action: "今すぐリセットする",
|
||||||
Confirm: "すべての設定をリセットしてもよろしいですか?",
|
Confirm: "すべての設定項目をリセットしてもよろしいですか?",
|
||||||
},
|
},
|
||||||
Clear: {
|
Clear: {
|
||||||
Title: "すべてのデータをクリア",
|
Title: "データを消去",
|
||||||
SubTitle: "すべてのチャット、設定データをクリア",
|
SubTitle: "すべてのチャット履歴と設定を消去します",
|
||||||
Action: "今すぐクリア",
|
Action: "今すぐ消去する",
|
||||||
Confirm: "すべてのチャット、設定データをクリアしてもよろしいですか?",
|
Confirm: "すべてのチャット履歴と設定を消去しますか?",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "すべての言語",
|
All: "全ての言語",
|
||||||
},
|
},
|
||||||
Avatar: "アバター",
|
Avatar: "アバター",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "フォントサイズ",
|
Title: "フォントサイズ",
|
||||||
SubTitle: "チャット内容のフォントサイズ",
|
SubTitle: "チャット内容のフォントサイズ",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "チャットフォント",
|
|
||||||
SubTitle:
|
|
||||||
"チャットコンテンツのフォント、空白の場合はグローバルデフォルトフォントを適用します",
|
|
||||||
Placeholder: "フォント名",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "システムプロンプトの注入",
|
Title: "システムプロンプトの挿入",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"すべてのリクエストメッセージリストの先頭にChatGPTのシステムプロンプトを強制的に追加",
|
"各リクエストのメッセージリストの先頭に、ChatGPTのシステムプロンプトを強制的に追加します",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "ユーザー入力のプリプロセス",
|
Title: "入力の前処理",
|
||||||
SubTitle: "最新のメッセージをこのテンプレートに埋め込む",
|
SubTitle: "新規入力がこのテンプレートに埋め込まれます",
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `現在のバージョン:${x}`,
|
Version: (x: string) => `現在のバージョン:${x}`,
|
||||||
IsLatest: "最新バージョンです",
|
IsLatest: "最新バージョンです",
|
||||||
CheckUpdate: "更新を確認",
|
CheckUpdate: "アップデートを確認",
|
||||||
IsChecking: "更新を確認中...",
|
IsChecking: "アップデートを確認しています...",
|
||||||
FoundUpdate: (x: string) => `新しいバージョンを発見:${x}`,
|
FoundUpdate: (x: string) => `新しいバージョンが見つかりました:${x}`,
|
||||||
GoToUpdate: "更新へ進む",
|
GoToUpdate: "更新する",
|
||||||
},
|
},
|
||||||
SendKey: "送信キー",
|
SendKey: "送信キー",
|
||||||
Theme: "テーマ",
|
Theme: "テーマ",
|
||||||
TightBorder: "ボーダーレスモード",
|
TightBorder: "ボーダーレスモード",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "プレビューバブル",
|
Title: "プレビューバブルの送信",
|
||||||
SubTitle: "プレビューバブルでMarkdownコンテンツをプレビュー",
|
SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "自動タイトル生成",
|
|
||||||
SubTitle: "チャット内容に基づいて適切なタイトルを生成",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "クラウドデータ",
|
|
||||||
NotSyncYet: "まだ同期されていません",
|
|
||||||
Success: "同期に成功しました",
|
|
||||||
Fail: "同期に失敗しました",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "クラウド同期の設定",
|
|
||||||
Check: "可用性を確認",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "同期タイプ",
|
|
||||||
SubTitle: "好きな同期サーバーを選択",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "プロキシを有効化",
|
|
||||||
SubTitle:
|
|
||||||
"ブラウザで同期する場合、クロスオリジン制限を避けるためにプロキシを有効にする必要があります",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "プロキシURL",
|
|
||||||
SubTitle: "このプロジェクトに組み込まれたクロスオリジンプロキシ専用",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV エンドポイント",
|
|
||||||
UserName: "ユーザー名",
|
|
||||||
Password: "パスワード",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST URL",
|
|
||||||
UserName: "バックアップ名",
|
|
||||||
Password: "UpStash Redis REST トークン",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "ローカルデータ",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} 回の対話、${overview.message} 件のメッセージ、${overview.prompt} 件のプロンプト、${overview.mask} 件のマスク`;
|
|
||||||
},
|
|
||||||
ImportFailed: "インポートに失敗しました",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "マスク起動画面",
|
Title: "キャラクターページ",
|
||||||
SubTitle: "新しいチャットを作成する際にマスク起動画面を表示",
|
SubTitle: "新規チャット作成時にキャラクターページを表示する",
|
||||||
},
|
},
|
||||||
Builtin: {
|
Builtin: {
|
||||||
Title: "内蔵マスクを非表示",
|
Title: "ビルトインマスクを非表示",
|
||||||
SubTitle: "すべてのマスクリストで内蔵マスクを非表示",
|
SubTitle: "マスクリストからビルトインを非表示する",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "プロンプトの自動補完を無効化",
|
Title: "プロンプトの自動補完を無効にする",
|
||||||
SubTitle: "入力フィールドの先頭に / を入力して自動補完をトリガー",
|
SubTitle:
|
||||||
|
"入力フィールドの先頭に / を入力すると、自動補完がトリガーされます。",
|
||||||
},
|
},
|
||||||
List: "カスタムプロンプトリスト",
|
List: "カスタムプロンプトリスト",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`内蔵 ${builtin} 件、ユーザー定義 ${custom} 件`,
|
`組み込み ${builtin} 件、ユーザー定義 ${custom} 件`,
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "プロンプトリスト",
|
Title: "プロンプトリスト",
|
||||||
Add: "新規作成",
|
Add: "新規追加",
|
||||||
Search: "プロンプトを検索",
|
Search: "プロンプトワード検索",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "プロンプトを編集",
|
Title: "編集",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "履歴メッセージ数",
|
Title: "履歴メッセージ数を添付",
|
||||||
SubTitle: "各リクエストに含まれる履歴メッセージの数",
|
SubTitle: "リクエストごとに添付する履歴メッセージ数",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "履歴メッセージの圧縮閾値",
|
Title: "履歴メッセージの長さ圧縮しきい値",
|
||||||
SubTitle: "未圧縮の履歴メッセージがこの値を超えた場合、圧縮が行われます",
|
SubTitle:
|
||||||
|
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "残高確認",
|
Title: "残高照会",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `今月の使用量 $${used}、サブスクリプション合計 $${total}`;
|
return `今月は $${used} を使用しました。総額は $${total} です。`;
|
||||||
},
|
},
|
||||||
IsChecking: "確認中…",
|
IsChecking: "確認中...",
|
||||||
Check: "再確認",
|
Check: "再確認",
|
||||||
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を確認",
|
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "アクセスパスワード",
|
|
||||||
SubTitle: "管理者が暗号化アクセスを有効にしました",
|
|
||||||
Placeholder: "アクセスパスワードを入力してください",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "カスタムエンドポイント",
|
|
||||||
SubTitle: "カスタムAzureまたはOpenAIサービスを使用するかどうか",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "モデルプロバイダー",
|
|
||||||
SubTitle: "異なるプロバイダーに切り替える",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "カスタムOpenAIキーを使用してパスワードアクセス制限を回避",
|
|
||||||
Placeholder: "OpenAI APIキー",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle:
|
|
||||||
"デフォルト以外のアドレスにはhttp(s)://を含める必要があります",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "カスタムAzureキーを使用してパスワードアクセス制限を回避",
|
|
||||||
Placeholder: "Azure APIキー",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "例:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "APIバージョン (azure api version)",
|
|
||||||
SubTitle: "特定のバージョンを選択",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle:
|
|
||||||
"カスタムAnthropicキーを使用してパスワードアクセス制限を回避",
|
|
||||||
Placeholder: "Anthropic APIキー",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "例:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "APIバージョン (claude api version)",
|
|
||||||
SubTitle: "特定のAPIバージョンを選択",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "Google AIからAPIキーを取得",
|
|
||||||
Placeholder: "Google AI Studio APIキーを入力",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "例:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "APIバージョン(gemini-pro専用)",
|
|
||||||
SubTitle: "特定のAPIバージョンを選択",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google セーフティ設定",
|
|
||||||
SubTitle: "コンテンツフィルタリングレベルを設定",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "カスタムBaidu APIキーを使用",
|
|
||||||
Placeholder: "Baidu APIキー",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "シークレットキー",
|
|
||||||
SubTitle: "カスタムBaiduシークレットキーを使用",
|
|
||||||
Placeholder: "Baiduシークレットキー",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "カスタムはサポートしていません、.env設定に進んでください",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "カスタムByteDance APIキーを使用",
|
|
||||||
Placeholder: "ByteDance APIキー",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "APIキー",
|
|
||||||
SubTitle: "カスタムAlibaba Cloud APIキーを使用",
|
|
||||||
Placeholder: "Alibaba Cloud APIキー",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "エンドポイント",
|
|
||||||
SubTitle: "例:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "カスタムモデル名",
|
|
||||||
SubTitle: "カスタムモデルの選択肢を追加、英語のカンマで区切る",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "モデル (model)",
|
Model: "モデル (model)",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "ランダム性 (temperature)",
|
Title: "ランダム性 (temperature)",
|
||||||
SubTitle: "値が大きいほど応答がランダムになります",
|
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "トップP (top_p)",
|
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"ランダム性に似ていますが、ランダム性と一緒に変更しないでください",
|
"値が大きいほど、回答がランダムになります。1以上の値には文字化けが含まれる可能性があります。",
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "1回の応答制限 (max_tokens)",
|
Title: "シングルレスポンス制限 (max_tokens)",
|
||||||
SubTitle: "1回の対話で使用される最大トークン数",
|
SubTitle: "1回のインタラクションで使用される最大トークン数",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "新鮮度 (presence_penalty)",
|
Title: "トピックの新鮮度 (presence_penalty)",
|
||||||
SubTitle: "値が大きいほど新しいトピックに移行する可能性が高くなります",
|
SubTitle: "値が大きいほど、新しいトピックへの展開が可能になります。",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "頻度ペナルティ (frequency_penalty)",
|
Title: "話題の頻度 (frequency_penalty)",
|
||||||
SubTitle: "値が大きいほど繰り返しの単語が減少します",
|
SubTitle: "値が大きいほど、重複語を低減する可能性が高くなります",
|
||||||
|
},
|
||||||
|
AutoGenerateTitle: {
|
||||||
|
Title: "タイトルの自動生成",
|
||||||
|
SubTitle: "会話内容に基づいて適切なタイトルを生成する",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "新しいチャット",
|
DefaultTopic: "新しいチャット",
|
||||||
BotHello: "何かお手伝いできますか?",
|
BotHello: "何かお手伝いできることはありますか",
|
||||||
Error: "エラーが発生しました。後でもう一度試してください",
|
Error: "エラーが発生しました。しばらくしてからやり直してください。",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"これは前提としての履歴チャットの要約です:" + content,
|
"これは、AI とユーザの過去のチャットを要約した前提となるストーリーです:" +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"この文の簡潔なテーマを四から五文字で返してください。説明、句読点、感嘆詞、余計なテキストは不要です。太字も不要です。テーマがない場合は「雑談」と返してください",
|
"4~5文字でこの文章の簡潔な主題を返してください。説明、句読点、感嘆詞、余分なテキストは無しで。もし主題がない場合は、「おしゃべり」を返してください",
|
||||||
Summarize:
|
Summarize:
|
||||||
"対話の内容を簡潔に要約し、後続のコンテキストプロンプトとして使用します。200文字以内に抑えてください",
|
"あなたとユーザの会話を簡潔にまとめて、後続のコンテキストプロンプトとして使ってください。200字以内に抑えてください。",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "クリップボードに書き込みました",
|
Success: "クリップボードに書き込みました",
|
||||||
Failed: "コピーに失敗しました。クリップボードの権限を付与してください",
|
Failed: "コピーに失敗しました。クリップボード許可を与えてください。",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "内容がダウンロードされました",
|
|
||||||
Failed: "ダウンロードに失敗しました",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `${x} 件のプリセットプロンプトが含まれています`,
|
Toast: (x: any) => `キャラクターが ${x} 件設定されました`,
|
||||||
Edit: "現在の対話設定",
|
Edit: "キャラクタープリセットとモデル設定",
|
||||||
Add: "対話を追加",
|
Add: "追加",
|
||||||
Clear: "コンテキストがクリアされました",
|
|
||||||
Revert: "コンテキストを元に戻す",
|
|
||||||
},
|
|
||||||
Plugin: {
|
|
||||||
Name: "プラグイン",
|
|
||||||
},
|
|
||||||
FineTuned: {
|
|
||||||
Sysmessage: "あなたはアシスタントです",
|
|
||||||
},
|
},
|
||||||
|
Plugin: { Name: "プラグイン" },
|
||||||
|
FineTuned: { Sysmessage: "あなたはアシスタントです" },
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "マスク",
|
Name: "キャラクタープリセット",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "プリセットキャラクターマスク",
|
Title: "キャラクタープリセット",
|
||||||
SubTitle: (count: number) => `${count} 件のプリセットキャラクター定義`,
|
SubTitle: (count: number) => `${count} 件見つかりました。`,
|
||||||
Search: "キャラクターマスクを検索",
|
Search: "検索",
|
||||||
Create: "新規作成",
|
Create: "新規",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `${count} 件のプリセット対話が含まれています`,
|
Info: (count: number) => `包含 ${count} 条预设对话`,
|
||||||
Chat: "対話",
|
Chat: "会話",
|
||||||
View: "表示",
|
View: "詳細",
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
Delete: "削除",
|
Delete: "削除",
|
||||||
DeleteConfirm: "削除してもよろしいですか?",
|
DeleteConfirm: "本当に削除しますか?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`プリセットマスクの編集 ${readonly ? "(読み取り専用)" : ""}`,
|
`キャラクタープリセットを編集 ${readonly ? "(読み取り専用)" : ""}`,
|
||||||
Download: "プリセットをダウンロード",
|
Download: "ダウンロード",
|
||||||
Clone: "プリセットをクローン",
|
Clone: "複製",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "キャラクターアバター",
|
Avatar: "キャラクターのアイコン",
|
||||||
Name: "キャラクター名",
|
Name: "キャラクターの名前",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "グローバル設定を使用",
|
Title: "グローバル設定を利用する",
|
||||||
SubTitle: "現在の対話でグローバルモデル設定を使用するかどうか",
|
SubTitle: "このチャットでグローバル設定を利用します。",
|
||||||
Confirm:
|
Confirm:
|
||||||
"現在の対話のカスタム設定が自動的に上書きされます。グローバル設定を有効にしてもよろしいですか?",
|
"カスタム設定を上書きしてグローバル設定を使用します、よろしいですか?",
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "プリセット対話を非表示",
|
Title: "キャラクター設定を表示しない",
|
||||||
SubTitle:
|
SubTitle: "チャット画面でのキャラクター設定を非表示にします。",
|
||||||
"非表示にすると、プリセット対話はチャット画面に表示されません",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "このマスクを共有",
|
|
||||||
SubTitle: "このマスクの直リンクを生成",
|
|
||||||
Action: "リンクをコピー",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "戻る",
|
Return: "戻る",
|
||||||
Skip: "直接開始",
|
Skip: "スキップ",
|
||||||
NotShow: "今後表示しない",
|
Title: "キャラクター",
|
||||||
ConfirmNoShow:
|
SubTitle: "さあ、AIにキャラクターを設定して会話を始めてみましょう",
|
||||||
"無効にしてもよろしいですか?無効にした後、設定でいつでも再度有効にできます。",
|
More: "もっと探す",
|
||||||
Title: "マスクを選択",
|
NotShow: "今後は表示しない",
|
||||||
SubTitle: "今すぐ始めよう、マスクの背後にある魂と思考の衝突",
|
ConfirmNoShow: "いつでも設定から有効化できます。",
|
||||||
More: "すべて表示",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "リンクにアクセスコードが含まれています。自動入力しますか?",
|
|
||||||
Settings: "リンクにプリセット設定が含まれています。自動入力しますか?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
Cancel: "キャンセル",
|
Cancel: "キャンセル",
|
||||||
Close: "閉じる",
|
Close: "閉じる",
|
||||||
Create: "新規作成",
|
Create: "新規",
|
||||||
Edit: "編集",
|
Edit: "編集",
|
||||||
Export: "エクスポート",
|
|
||||||
Import: "インポート",
|
|
||||||
Sync: "同期",
|
|
||||||
Config: "設定",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "コンテキストをクリアした後のメッセージのみが表示されます",
|
|
||||||
},
|
|
||||||
Model: "モデル",
|
Model: "モデル",
|
||||||
Messages: "メッセージ",
|
Messages: "メッセージ",
|
||||||
Topic: "テーマ",
|
Topic: "トピック",
|
||||||
Time: "時間",
|
Time: "時間",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,258 +1,115 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
import type { PartialLocaleType } from "./index";
|
||||||
|
|
||||||
const ko: PartialLocaleType = {
|
const ko: PartialLocaleType = {
|
||||||
WIP: "곧 출시 예정...",
|
WIP: "곧 출시 예정...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "유효하지 않은 API 키가 감지되었습니다. [설정](/#/settings) 페이지에서 API 키가 올바르게 구성되었는지 확인하십시오."
|
"권한이 없습니다. 설정 페이지에서 액세스 코드를 [입력하세요](/#/auth).",
|
||||||
: "잘못된 접근 비밀번호이거나 비밀번호가 비어 있습니다. [로그인](/#/auth) 페이지에서 올바른 접근 비밀번호를 입력하거나 [설정](/#/settings) 페이지에서 OpenAI API 키를 입력하십시오.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "비밀번호 필요",
|
|
||||||
Tips: "관리자가 비밀번호 인증을 활성화했습니다. 아래에 접근 코드를 입력하십시오.",
|
|
||||||
SubTips: "또는 OpenAI 또는 Google API 키를 입력하십시오.",
|
|
||||||
Input: "여기에 접근 코드를 입력하십시오.",
|
|
||||||
Confirm: "확인",
|
|
||||||
Later: "나중에 하기",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 개의 대화`,
|
ChatItemCount: (count: number) => `${count}개의 메시지`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `총 ${count} 개의 대화`,
|
SubTitle: (count: number) => `ChatGPT와의 ${count}개의 메시지`,
|
||||||
EditMessage: {
|
|
||||||
Title: "메시지 기록 편집",
|
|
||||||
Topic: {
|
|
||||||
Title: "채팅 주제",
|
|
||||||
SubTitle: "현재 채팅 주제 변경",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "메시지 목록 보기",
|
ChatList: "채팅 목록으로 이동",
|
||||||
CompressedHistory: "압축된 히스토리 프롬프트 보기",
|
CompressedHistory: "압축된 기억력 메모리 프롬프트",
|
||||||
Export: "채팅 기록 내보내기",
|
Export: "모든 메시지를 Markdown으로 내보내기",
|
||||||
Copy: "복사",
|
Copy: "복사",
|
||||||
Stop: "정지",
|
Stop: "중지",
|
||||||
Retry: "다시 시도",
|
Retry: "다시 시도",
|
||||||
Pin: "고정",
|
|
||||||
PinToastContent: "1 개의 대화를 프롬프트에 고정했습니다.",
|
|
||||||
PinToastAction: "보기",
|
|
||||||
Delete: "삭제",
|
Delete: "삭제",
|
||||||
Edit: "편집",
|
|
||||||
},
|
|
||||||
Commands: {
|
|
||||||
new: "새 채팅",
|
|
||||||
newm: "마스크에서 새 채팅",
|
|
||||||
next: "다음 채팅",
|
|
||||||
prev: "이전 채팅",
|
|
||||||
clear: "컨텍스트 지우기",
|
|
||||||
del: "채팅 삭제",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "응답 중지",
|
|
||||||
ToBottom: "최신으로 스크롤",
|
|
||||||
Theme: {
|
|
||||||
auto: "자동 테마",
|
|
||||||
light: "라이트 모드",
|
|
||||||
dark: "다크 모드",
|
|
||||||
},
|
|
||||||
Prompt: "빠른 명령",
|
|
||||||
Masks: "모든 마스크",
|
|
||||||
Clear: "채팅 지우기",
|
|
||||||
Settings: "채팅 설정",
|
|
||||||
UploadImage: "이미지 업로드",
|
|
||||||
},
|
},
|
||||||
Rename: "채팅 이름 변경",
|
Rename: "채팅 이름 변경",
|
||||||
Typing: "입력 중…",
|
Typing: "입력 중...",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} 전송`;
|
var inputHints = `${submitKey}를 눌러 보내기`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter 줄 바꿈";
|
inputHints += ", Shift + Enter로 줄 바꿈";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ 자동 완성,: 명령어 입력";
|
return inputHints + ", 프롬프트 검색을 위해 / 입력";
|
||||||
},
|
},
|
||||||
Send: "전송",
|
Send: "보내기",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "기억 지우기",
|
Reset: "기본값으로 재설정",
|
||||||
SaveAs: "마스크로 저장",
|
SaveAs: "마스크로 저장",
|
||||||
},
|
},
|
||||||
IsContext: "프롬프트 설정",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "채팅 기록 공유",
|
Title: "모든 메시지",
|
||||||
Copy: "모두 복사",
|
Copy: "모두 복사",
|
||||||
Download: "파일 다운로드",
|
Download: "다운로드",
|
||||||
Share: "ShareGPT에 공유",
|
MessageFromYou: "나의 메시지",
|
||||||
MessageFromYou: "사용자",
|
MessageFromChatGPT: "ChatGPT의 메시지",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "내보내기 형식",
|
|
||||||
SubTitle: "Markdown 텍스트 또는 PNG 이미지로 내보낼 수 있습니다.",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "프롬프트 컨텍스트 포함",
|
|
||||||
SubTitle: "메시지에 프롬프트 컨텍스트를 표시할지 여부",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "선택",
|
|
||||||
Preview: "미리보기",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "스크린샷 생성 중",
|
|
||||||
Modal: "길게 누르거나 오른쪽 클릭하여 이미지를 저장하십시오.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "메시지 검색",
|
|
||||||
All: "모두 선택",
|
|
||||||
Latest: "최근 몇 개",
|
|
||||||
Clear: "선택 지우기",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "기록 요약",
|
Title: "기억 프롬프트",
|
||||||
EmptyContent: "대화 내용이 너무 짧아 요약할 필요 없음",
|
EmptyContent: "아직 내용이 없습니다.",
|
||||||
Send: "자동으로 채팅 기록을 압축하여 컨텍스트로 전송",
|
Send: "기억 보내기",
|
||||||
Copy: "요약 복사",
|
Copy: "기억 복사",
|
||||||
Reset: "[사용되지 않음]",
|
Reset: "세션 재설정",
|
||||||
ResetConfirm: "기록 요약을 지우겠습니까?",
|
ResetConfirm:
|
||||||
|
"재설정하면 현재 대화 기록과 기억력이 삭제됩니다. 정말 재설정하시겠습니까?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "새 채팅",
|
NewChat: "새로운 채팅",
|
||||||
DeleteChat: "선택한 대화를 삭제하시겠습니까?",
|
DeleteChat: "선택한 대화를 삭제하시겠습니까?",
|
||||||
DeleteToast: "대화가 삭제되었습니다.",
|
DeleteToast: "채팅이 삭제되었습니다.",
|
||||||
Revert: "되돌리기",
|
Revert: "되돌리기",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "설정",
|
Title: "설정",
|
||||||
SubTitle: "모든 설정 옵션",
|
SubTitle: "모든 설정",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "모든 설정 초기화",
|
|
||||||
SubTitle: "모든 설정 항목을 기본값으로 초기화",
|
|
||||||
Action: "지금 초기화",
|
|
||||||
Confirm: "모든 설정을 초기화하시겠습니까?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "모든 데이터 지우기",
|
|
||||||
SubTitle: "모든 채팅 및 설정 데이터 지우기",
|
|
||||||
Action: "지금 지우기",
|
|
||||||
Confirm: "모든 채팅 및 설정 데이터를 지우시겠습니까?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // 주의: 새 번역을 추가하려면 이 값을 번역하지 말고 그대로 유지하세요.
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "모든 언어",
|
All: "All Languages",
|
||||||
},
|
},
|
||||||
Avatar: "아바타",
|
Avatar: "아바타",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "글꼴 크기",
|
Title: "글꼴 크기",
|
||||||
SubTitle: "채팅 내용의 글꼴 크기",
|
SubTitle: "채팅 내용의 글꼴 크기 조정",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "채팅 폰트",
|
|
||||||
SubTitle: "채팅 내용의 폰트, 비워 두면 글로벌 기본 폰트를 적용",
|
|
||||||
Placeholder: "폰트 이름",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "시스템 수준 프롬프트 삽입",
|
Title: "시스템 프롬프트 주입",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"각 요청 메시지 목록의 시작 부분에 ChatGPT 시스템 프롬프트를 강제로 추가",
|
"각 요청의 메시지 목록의 시작에 ChatGPT 시스템 프롬프트를 강제로 추가합니다",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "사용자 입력 전처리",
|
|
||||||
SubTitle: "사용자의 최신 메시지가 이 템플릿에 채워집니다.",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `현재 버전: ${x}`,
|
Version: (x: string) => `버전: ${x}`,
|
||||||
IsLatest: "최신 버전입니다.",
|
IsLatest: "최신 버전",
|
||||||
CheckUpdate: "업데이트 확인",
|
CheckUpdate: "업데이트 확인",
|
||||||
IsChecking: "업데이트 확인 중...",
|
IsChecking: "업데이트 확인 중...",
|
||||||
FoundUpdate: (x: string) => `새 버전 발견: ${x}`,
|
FoundUpdate: (x: string) => `새 버전 발견: ${x}`,
|
||||||
GoToUpdate: "업데이트로 이동",
|
GoToUpdate: "업데이트",
|
||||||
},
|
},
|
||||||
SendKey: "전송 키",
|
SendKey: "전송 키",
|
||||||
Theme: "테마",
|
Theme: "테마",
|
||||||
TightBorder: "테두리 없는 모드",
|
TightBorder: "조밀한 테두리",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "미리보기 버블",
|
Title: "미리 보기 버블 전송",
|
||||||
SubTitle: "미리보기 버블에서 Markdown 콘텐츠 미리보기",
|
SubTitle: "버블에서 마크다운 미리 보기",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "제목 자동 생성",
|
|
||||||
SubTitle: "대화 내용에 따라 적절한 제목 생성",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "클라우드 데이터",
|
|
||||||
NotSyncYet: "아직 동기화되지 않았습니다.",
|
|
||||||
Success: "동기화 성공",
|
|
||||||
Fail: "동기화 실패",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "클라우드 동기화 구성",
|
|
||||||
Check: "사용 가능성 확인",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "동기화 유형",
|
|
||||||
SubTitle: "선호하는 동기화 서버 선택",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "프록시 사용",
|
|
||||||
SubTitle:
|
|
||||||
"브라우저에서 동기화할 때 프록시를 활성화하여 교차 출처 제한을 피해야 함",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "프록시 주소",
|
|
||||||
SubTitle: "이 프로젝트에서 제공하는 교차 출처 프록시만 해당",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV 주소",
|
|
||||||
UserName: "사용자 이름",
|
|
||||||
Password: "비밀번호",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST URL",
|
|
||||||
UserName: "백업 이름",
|
|
||||||
Password: "UpStash Redis REST Token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "로컬 데이터",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} 회의 대화, ${overview.message} 개의 메시지, ${overview.prompt} 개의 프롬프트, ${overview.mask} 개의 마스크`;
|
|
||||||
},
|
|
||||||
ImportFailed: "가져오기 실패",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "마스크 시작 페이지",
|
Title: "마스크 시작 화면",
|
||||||
SubTitle: "새 채팅 시 마스크 시작 페이지 표시",
|
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "내장 마스크 숨기기",
|
|
||||||
SubTitle: "모든 마스크 목록에서 내장 마스크 숨기기",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "프롬프트 자동 완성 비활성화",
|
Title: "자동 완성 비활성화",
|
||||||
SubTitle: "입력 상자 시작 부분에 / 를 입력하여 자동 완성 활성화",
|
SubTitle: "자동 완성을 활성화하려면 /를 입력하세요.",
|
||||||
},
|
},
|
||||||
List: "사용자 정의 프롬프트 목록",
|
List: "프롬프트 목록",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`내장 ${builtin} 개, 사용자 정의 ${custom} 개`,
|
`내장 ${builtin}개, 사용자 정의 ${custom}개`,
|
||||||
Edit: "편집",
|
Edit: "편집",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "프롬프트 목록",
|
Title: "프롬프트 목록",
|
||||||
Add: "새로 만들기",
|
Add: "추가",
|
||||||
Search: "프롬프트 검색",
|
Search: "프롬프트 검색",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
@@ -260,278 +117,116 @@ const ko: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "히스토리 메시지 수",
|
Title: "첨부된 메시지 수",
|
||||||
SubTitle: "각 요청에 포함된 히스토리 메시지 수",
|
SubTitle: "요청당 첨부된 전송된 메시지 수",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "히스토리 메시지 길이 압축 임계값",
|
Title: "기록 압축 임계값",
|
||||||
SubTitle: "압축되지 않은 히스토리 메시지가 이 값을 초과하면 압축 수행",
|
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "잔액 조회",
|
Title: "계정 잔액",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `이번 달 사용된 금액: $${used},총 구독 금액: $${total}`;
|
return `이번 달 사용액 ${used}, 구독액 ${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "확인 중...",
|
IsChecking: "확인 중...",
|
||||||
Check: "다시 확인",
|
Check: "확인",
|
||||||
NoAccess: "잔액을 보려면 API 키 또는 접근 비밀번호를 입력하십시오.",
|
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "모델",
|
||||||
AccessCode: {
|
|
||||||
Title: "접근 비밀번호",
|
|
||||||
SubTitle: "관리자가 암호화된 접근을 활성화했습니다.",
|
|
||||||
Placeholder: "접근 비밀번호를 입력하십시오.",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "커스텀 엔드포인트",
|
|
||||||
SubTitle: "커스텀 Azure 또는 OpenAI 서비스를 사용할지 여부",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "모델 서비스 제공업체",
|
|
||||||
SubTitle: "다른 서비스 제공업체로 전환",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API 키",
|
|
||||||
SubTitle: "커스텀 OpenAI 키를 사용하여 비밀번호 접근 제한 우회",
|
|
||||||
Placeholder: "OpenAI API 키",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "기본 주소 외에 http(s)://을 포함해야 함",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "엔드포인트 키",
|
|
||||||
SubTitle: "커스텀 Azure 키를 사용하여 비밀번호 접근 제한 우회",
|
|
||||||
Placeholder: "Azure API 키",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "예: ",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API 버전 (azure api version)",
|
|
||||||
SubTitle: "특정 부분 버전 선택",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "엔드포인트 키",
|
|
||||||
SubTitle: "커스텀 Anthropic 키를 사용하여 비밀번호 접근 제한 우회",
|
|
||||||
Placeholder: "Anthropic API 키",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "예: ",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API 버전 (claude api version)",
|
|
||||||
SubTitle: "특정 API 버전 입력",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API 키",
|
|
||||||
SubTitle: "Google AI에서 API 키를 가져오세요.",
|
|
||||||
Placeholder: "Google AI Studio API 키 입력",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "예: ",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "API 버전 (gemini-pro 전용)",
|
|
||||||
SubTitle: "특정 API 버전 선택",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google 안전 필터링 수준",
|
|
||||||
SubTitle: "콘텐츠 필터링 수준 설정",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API 키",
|
|
||||||
SubTitle: "커스텀 Baidu API 키 사용",
|
|
||||||
Placeholder: "Baidu API 키",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret 키",
|
|
||||||
SubTitle: "커스텀 Baidu Secret 키 사용",
|
|
||||||
Placeholder: "Baidu Secret 키",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "커스터마이즈는 .env에서 설정",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "엔드포인트 키",
|
|
||||||
SubTitle: "커스텀 ByteDance API 키 사용",
|
|
||||||
Placeholder: "ByteDance API 키",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "예: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "엔드포인트 키",
|
|
||||||
SubTitle: "커스텀 Alibaba Cloud API 키 사용",
|
|
||||||
Placeholder: "Alibaba Cloud API 키",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "엔드포인트 주소",
|
|
||||||
SubTitle: "예: ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "커스텀 모델 이름",
|
|
||||||
SubTitle: "커스텀 모델 옵션 추가, 영어 쉼표로 구분",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "모델 (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "무작위성 (temperature)",
|
Title: "온도 (temperature)",
|
||||||
SubTitle: "값이 클수록 응답이 더 무작위적",
|
SubTitle: "값이 클수록 더 무작위한 출력이 생성됩니다.",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "탑 P 샘플링 (top_p)",
|
|
||||||
SubTitle: "무작위성과 유사하지만, 무작위성과 함께 변경하지 마십시오.",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "단일 응답 제한 (max_tokens)",
|
Title: "최대 토큰 수 (max_tokens)",
|
||||||
SubTitle: "단일 상호작용에 사용되는 최대 토큰 수",
|
SubTitle: "입력 토큰과 생성된 토큰의 최대 길이",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "주제 신선도 (presence_penalty)",
|
Title: "존재 페널티 (presence_penalty)",
|
||||||
SubTitle: "값이 클수록 새로운 주제로 확장할 가능성이 높음",
|
SubTitle: "값이 클수록 새로운 주제에 대해 대화할 가능성이 높아집니다.",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "빈도 벌점 (frequency_penalty)",
|
Title: "빈도 페널티(frequency penalty)",
|
||||||
SubTitle: "값이 클수록 중복 단어 감소 가능성 높음",
|
SubTitle: "값이 클수록 같은 줄이 반복될 가능성이 줄어듭니다.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "새 채팅",
|
DefaultTopic: "새 대화",
|
||||||
BotHello: "무엇을 도와드릴까요?",
|
BotHello: "안녕하세요! 오늘 도움이 필요하신가요?",
|
||||||
Error: "오류가 발생했습니다. 나중에 다시 시도해 주세요.",
|
Error: "문제가 발생했습니다. 나중에 다시 시도해주세요.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) => "이것은 이전 채팅 요약입니다: " + content,
|
History: (content: string) =>
|
||||||
|
"이것은 AI와 사용자 간의 대화 기록을 요약한 내용입니다: " + content,
|
||||||
Topic:
|
Topic:
|
||||||
"네 글자에서 다섯 글자로 이 문장의 간략한 주제를 반환하세요. 설명이나 문장 부호, 어미, 불필요한 텍스트, 굵은 글씨는 필요 없습니다. 주제가 없다면 '잡담'이라고만 반환하세요.",
|
"다음과 같이 대화 내용을 요약하는 4~5단어 제목을 생성해주세요. 따옴표, 구두점, 인용부호, 기호 또는 추가 텍스트를 제거하십시오. 따옴표로 감싸진 부분을 제거하십시오.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"대화 내용을 간략히 요약하여 후속 컨텍스트 프롬프트로 사용하세요. 200자 이내로 작성하세요.",
|
"200단어 이내로 저희 토론을 간략히 요약하여 앞으로의 맥락으로 사용할 수 있는 프롬프트로 만들어주세요.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "클립보드에 복사되었습니다.",
|
Success: "클립보드에 복사되었습니다.",
|
||||||
Failed: "복사 실패, 클립보드 권한을 부여해주세요.",
|
Failed: "복사 실패, 클립보드 접근 권한을 허용해주세요.",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "내용이 디렉토리에 다운로드되었습니다.",
|
|
||||||
Failed: "다운로드 실패.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => ` ${x} 개의 프리셋 프롬프트 포함됨`,
|
Toast: (x: any) => `컨텍스트 프롬프트 ${x}개 사용`,
|
||||||
Edit: "현재 대화 설정",
|
Edit: "컨텍스트 및 메모리 프롬프트",
|
||||||
Add: "대화 추가",
|
Add: "프롬프트 추가",
|
||||||
Clear: "컨텍스트가 지워졌습니다.",
|
|
||||||
Revert: "컨텍스트 복원",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "플러그인",
|
Name: "플러그인",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "당신은 보조자입니다.",
|
Sysmessage: "당신은 어시스턴트입니다",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "마스크",
|
Name: "마스크",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "프리셋 캐릭터 마스크",
|
Title: "프롬프트 템플릿",
|
||||||
SubTitle: (count: number) => `${count} 개의 프리셋 캐릭터 정의`,
|
SubTitle: (count: number) => `${count}개의 프롬프트 템플릿`,
|
||||||
Search: "캐릭터 마스크 검색",
|
Search: "템플릿 검색",
|
||||||
Create: "새로 만들기",
|
Create: "생성",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => ` ${count} 개의 프리셋 대화 포함`,
|
Info: (count: number) => `${count}개의 프롬프롬프트`,
|
||||||
Chat: "대화",
|
Chat: "채팅",
|
||||||
View: "보기",
|
View: "보기",
|
||||||
Edit: "편집",
|
Edit: "편집",
|
||||||
Delete: "삭제",
|
Delete: "삭제",
|
||||||
DeleteConfirm: "삭제를 확인하시겠습니까?",
|
DeleteConfirm: "삭제하시겠습니까?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`프리셋 마스크 편집 ${readonly ? "(읽기 전용)" : ""}`,
|
`프롬프트 템플릿 편집 ${readonly ? "(읽기 전용)" : ""}`,
|
||||||
Download: "프리셋 다운로드",
|
Download: "다운로드",
|
||||||
Clone: "프리셋 복제",
|
Clone: "복제",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "캐릭터 아바타",
|
Avatar: "봇 아바타",
|
||||||
Name: "캐릭터 이름",
|
Name: "봇 이름",
|
||||||
Sync: {
|
|
||||||
Title: "전역 설정 사용",
|
|
||||||
SubTitle: "현재 대화가 전역 모델 설정을 사용하는지 여부",
|
|
||||||
Confirm:
|
|
||||||
"현재 대화의 사용자 정의 설정이 자동으로 덮어쓰여질 것입니다. 전역 설정을 활성화하시겠습니까?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "프리셋 대화 숨기기",
|
|
||||||
SubTitle: "숨기면 프리셋 대화가 채팅 화면에 나타나지 않습니다.",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "이 마스크 공유하기",
|
|
||||||
SubTitle: "이 마스크의 직접 링크 생성",
|
|
||||||
Action: "링크 복사",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "돌아가기",
|
Return: "돌아가기",
|
||||||
Skip: "바로 시작",
|
Skip: "건너뛰기",
|
||||||
NotShow: "다시 보지 않기",
|
|
||||||
ConfirmNoShow:
|
|
||||||
"비활성화하시겠습니까? 비활성화 후 언제든지 설정에서 다시 활성화할 수 있습니다.",
|
|
||||||
Title: "마스크 선택",
|
Title: "마스크 선택",
|
||||||
SubTitle: "지금 시작하여 마스크 뒤의 사고와 교류해보세요.",
|
SubTitle: "마스크 뒤의 영혼과 대화하세요",
|
||||||
More: "모두 보기",
|
More: "더 보기",
|
||||||
},
|
NotShow: "다시 표시하지 않음",
|
||||||
|
ConfirmNoShow:
|
||||||
URLCommand: {
|
"비활성화하시겠습니까? 나중에 설정에서 다시 활성화할 수 있습니다.",
|
||||||
Code: "링크에 액세스 코드가 포함되어 있습니다. 자동으로 입력하시겠습니까?",
|
|
||||||
Settings:
|
|
||||||
"링크에 프리셋 설정이 포함되어 있습니다. 자동으로 입력하시겠습니까?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "확인",
|
Confirm: "확인",
|
||||||
Cancel: "취소",
|
Cancel: "취소",
|
||||||
Close: "닫기",
|
Close: "닫기",
|
||||||
Create: "새로 만들기",
|
Create: "생성",
|
||||||
Edit: "편집",
|
Edit: "편집",
|
||||||
Export: "내보내기",
|
|
||||||
Import: "가져오기",
|
|
||||||
Sync: "동기화",
|
|
||||||
Config: "구성",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "컨텍스트가 지워진 후의 메시지만 표시됩니다.",
|
|
||||||
},
|
|
||||||
Model: "모델",
|
Model: "모델",
|
||||||
Messages: "메시지",
|
Messages: "메시지",
|
||||||
Topic: "주제",
|
Topic: "주제",
|
||||||
|
|||||||
@@ -1,557 +1,158 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const no: PartialLocaleType = {
|
const no: PartialLocaleType = {
|
||||||
WIP: "Arbeid pågår ...",
|
WIP: "Arbeid pågår ...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Ugyldig API-nøkkel oppdaget. Gå til [innstillinger](/#/settings) for å sjekke om API-nøkkelen er riktig konfigurert."
|
"Du har ikke tilgang. [Vennlig oppgi tildelt adgangskode](/#/auth).",
|
||||||
: "Adgangskoden er feil eller tom. Gå til [innlogging](/#/auth) for å oppgi riktig adgangskode, eller fyll inn din egen OpenAI API-nøkkel på [innstillinger](/#/settings) siden.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Passord påkrevd",
|
|
||||||
Tips: "Administrator har aktivert passordbeskyttelse. Vennligst skriv inn tilgangskoden nedenfor",
|
|
||||||
SubTips: "Eller skriv inn din OpenAI eller Google API-nøkkel",
|
|
||||||
Input: "Skriv tilgangskoden her",
|
|
||||||
Confirm: "Bekreft",
|
|
||||||
Later: "Kom tilbake senere",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} samtaler`,
|
ChatItemCount: (count: number) => `${count} meldinger`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Totalt ${count} samtaler`,
|
SubTitle: (count: number) => `${count} meldinger med ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Rediger meldingshistorikk",
|
|
||||||
Topic: {
|
|
||||||
Title: "Samtaleemne",
|
|
||||||
SubTitle: "Endre nåværende samtaleemne",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Vis meldingsliste",
|
ChatList: "Gå til chatlisten",
|
||||||
CompressedHistory: "Vis komprimert historikk",
|
CompressedHistory: "Komprimert historikk for instrukser",
|
||||||
Export: "Eksporter samtaler",
|
Export: "Eksporter alle meldinger i markdown-format",
|
||||||
Copy: "Kopier",
|
Copy: "Kopier",
|
||||||
Stop: "Stopp",
|
Stop: "Stopp",
|
||||||
Retry: "Prøv igjen",
|
Retry: "Prøv igjen",
|
||||||
Pin: "Fest",
|
|
||||||
PinToastContent:
|
|
||||||
"1 samtale er festet til forhåndsdefinerte oppfordringer",
|
|
||||||
PinToastAction: "Se",
|
|
||||||
Delete: "Slett",
|
Delete: "Slett",
|
||||||
Edit: "Rediger",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Gi nytt navn",
|
||||||
new: "Ny samtale",
|
Typing: "Skriver …",
|
||||||
newm: "Start samtale fra maske",
|
|
||||||
next: "Neste samtale",
|
|
||||||
prev: "Forrige samtale",
|
|
||||||
clear: "Rydd kontekst",
|
|
||||||
del: "Slett samtale",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Stopp respons",
|
|
||||||
ToBottom: "Rull til nyeste",
|
|
||||||
Theme: {
|
|
||||||
auto: "Automatisk tema",
|
|
||||||
light: "Lyst tema",
|
|
||||||
dark: "Mørkt tema",
|
|
||||||
},
|
|
||||||
Prompt: "Hurtigkommando",
|
|
||||||
Masks: "Alle masker",
|
|
||||||
Clear: "Rydd samtale",
|
|
||||||
Settings: "Samtaleinnstillinger",
|
|
||||||
UploadImage: "Last opp bilde",
|
|
||||||
},
|
|
||||||
Rename: "Gi nytt navn til samtale",
|
|
||||||
Typing: "Skriver…",
|
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} send`;
|
var inputHints = `${submitKey} for å sende`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter for linjeskift";
|
inputHints += ", Shift + Enter for å omgi";
|
||||||
}
|
}
|
||||||
return (
|
return inputHints + ", / for å søke instrukser";
|
||||||
inputHints + ",/ for å utløse autoutfylling, : for å utløse kommando"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
Send: "Send",
|
Send: "Send",
|
||||||
Config: {
|
|
||||||
Reset: "Fjern minne",
|
|
||||||
SaveAs: "Lagre som maske",
|
|
||||||
},
|
|
||||||
IsContext: "Forhåndsdefinerte oppfordringer",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Del samtalehistorikk",
|
Title: "Alle meldinger",
|
||||||
Copy: "Kopier alt",
|
Copy: "Kopiere alle",
|
||||||
Download: "Last ned fil",
|
Download: "Last ned",
|
||||||
Share: "Del til ShareGPT",
|
MessageFromYou: "Melding fra deg",
|
||||||
MessageFromYou: "Bruker",
|
MessageFromChatGPT: "Melding fra ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Eksporterformat",
|
|
||||||
SubTitle: "Kan eksporteres som Markdown-tekst eller PNG-bilde",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Inkluder maske kontekst",
|
|
||||||
SubTitle: "Skal maske kontekst vises i meldinger",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Velg",
|
|
||||||
Preview: "Forhåndsvis",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Genererer skjermbilde",
|
|
||||||
Modal: "Langtrykk eller høyreklikk for å lagre bilde",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Søk meldinger",
|
|
||||||
All: "Velg alle",
|
|
||||||
Latest: "Siste meldinger",
|
|
||||||
Clear: "Fjern valg",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Historisk sammendrag",
|
Title: "Minneinstruks",
|
||||||
EmptyContent: "Samtaleinnholdet er for kort, ingen oppsummering nødvendig",
|
EmptyContent: "Ingen sålant.",
|
||||||
Send: "Automatisk komprimere samtalehistorikk og sende som kontekst",
|
Send: "Send minne",
|
||||||
Copy: "Kopier sammendrag",
|
Copy: "Kopiere minne",
|
||||||
Reset: "[unused]",
|
Reset: "Nulstill sesjon",
|
||||||
ResetConfirm: "Er du sikker på at du vil tømme historisk sammendrag?",
|
ResetConfirm:
|
||||||
|
"Om du nillstiller vil du slette hele historikken. Er du sikker på at du vil nullstille?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Ny samtale",
|
NewChat: "Ny chat",
|
||||||
DeleteChat: "Er du sikker på at du vil slette den valgte samtalen?",
|
DeleteChat: "Bekreft for å slette det valgte dialogen",
|
||||||
DeleteToast: "Samtale slettet",
|
DeleteToast: "Samtale slettet",
|
||||||
Revert: "Angre",
|
Revert: "Tilbakestill",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Innstillinger",
|
Title: "Innstillinger",
|
||||||
SubTitle: "Alle innstillingsalternativer",
|
SubTitle: "Alle innstillinger",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Tilbakestill alle innstillinger",
|
|
||||||
SubTitle:
|
|
||||||
"Tilbakestill alle innstillingsalternativer til standardverdier",
|
|
||||||
Action: "Tilbakestill nå",
|
|
||||||
Confirm: "Bekreft tilbakestilling av alle innstillinger?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Slett alle data",
|
|
||||||
SubTitle: "Slett alle samtaler og innstillingsdata",
|
|
||||||
Action: "Slett nå",
|
|
||||||
Confirm: "Bekreft sletting av alle samtaler og innstillingsdata?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Alle språk",
|
|
||||||
},
|
},
|
||||||
Avatar: "Profilbilde",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Skriftstørrelse",
|
Title: "Fontstørrelsen",
|
||||||
SubTitle: "Skriftstørrelse for samtaleinnhold",
|
SubTitle: "Juster fontstørrelsen for samtaleinnholdet.",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Chat-skrifttype",
|
|
||||||
SubTitle:
|
|
||||||
"Skrifttypen for chatinnhold, la stå tom for å bruke global standardskrifttype",
|
|
||||||
Placeholder: "Skriftnavn",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Injiser systemprompter",
|
Title: "Sett inn systemprompter",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Tving inn et systemprompt som simulerer ChatGPT i starten av hver forespørsel",
|
"Tving tillegg av en simulert ChatGPT-systemprompt i begynnelsen av meldingslisten for hver forespørsel",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Forhåndsbehandling av brukerinput",
|
|
||||||
SubTitle:
|
|
||||||
"Den nyeste meldingen fra brukeren vil bli fylt ut i denne malen",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Nåværende versjon: ${x}`,
|
Version: (x: string) => `Versjon: ${x}`,
|
||||||
IsLatest: "Er den nyeste versjonen",
|
IsLatest: "Siste versjon",
|
||||||
CheckUpdate: "Sjekk oppdateringer",
|
CheckUpdate: "Se etter oppdatering",
|
||||||
IsChecking: "Sjekker oppdateringer...",
|
IsChecking: "Ser etter oppdatering ...",
|
||||||
FoundUpdate: (x: string) => `Ny versjon oppdaget: ${x}`,
|
FoundUpdate: (x: string) => `Fant ny versjon: ${x}`,
|
||||||
GoToUpdate: "Gå til oppdatering",
|
GoToUpdate: "Oppdater",
|
||||||
},
|
},
|
||||||
SendKey: "Send-knapp",
|
SendKey: "Send nøkkel",
|
||||||
Theme: "Tema",
|
Theme: "Tema",
|
||||||
TightBorder: "Ingen ramme-modus",
|
TightBorder: "Stram innramming",
|
||||||
SendPreviewBubble: {
|
|
||||||
Title: "Forhåndsvisningsboble",
|
|
||||||
SubTitle: "Forhåndsvis Markdown-innhold i en forhåndsvisningsboble",
|
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Automatisk generere tittel",
|
|
||||||
SubTitle: "Generer en passende tittel basert på samtaleinnholdet",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Skydatasynkronisering",
|
|
||||||
NotSyncYet: "Har ikke blitt synkronisert ennå",
|
|
||||||
Success: "Synkronisering vellykket",
|
|
||||||
Fail: "Synkronisering mislyktes",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Konfigurer sky-synkronisering",
|
|
||||||
Check: "Sjekk tilgjengelighet",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Synkroniseringstype",
|
|
||||||
SubTitle: "Velg ønsket synkroniseringsserver",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Aktiver proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Når du synkroniserer i nettleseren, må proxy være aktivert for å unngå CORS-restriksjoner",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Proxy-URL",
|
|
||||||
SubTitle: "Kun for prosjektets innebygde CORS-proxy",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV-adresse",
|
|
||||||
UserName: "Brukernavn",
|
|
||||||
Password: "Passord",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST-URL",
|
|
||||||
UserName: "Sikkerhetskopinavn",
|
|
||||||
Password: "UpStash Redis REST-token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Lokal data",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} samtaler, ${overview.message} meldinger, ${overview.prompt} oppfordringer, ${overview.mask} masker`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Import mislyktes",
|
|
||||||
},
|
|
||||||
Mask: {
|
|
||||||
Splash: {
|
|
||||||
Title: "Maskestartside",
|
|
||||||
SubTitle: "Vis maskestartside når du oppretter en ny samtale",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Skjul innebygde masker",
|
|
||||||
SubTitle: "Skjul innebygde masker i alle maskelister",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Deaktiver automatisk fullføring av oppfordringer",
|
Title: "Skru av autofullfør",
|
||||||
SubTitle: "Skriv / i tekstboksen for å utløse automatisk fullføring",
|
SubTitle: "Skriv / for å trigge autofullfør",
|
||||||
},
|
},
|
||||||
List: "Egendefinerte oppfordringer",
|
List: "Instruksliste",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Innebygde ${builtin}, brukerdedefinerte ${custom}`,
|
`${builtin} innebygde, ${custom} brukerdefinerte`,
|
||||||
Edit: "Rediger",
|
Edit: "Endre",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Oppfordringsliste",
|
Title: "Instruksliste",
|
||||||
Add: "Ny",
|
Add: "Legg til",
|
||||||
Search: "Søk oppfordringer",
|
Search: "Søk instrukser",
|
||||||
},
|
|
||||||
EditModal: {
|
|
||||||
Title: "Rediger oppfordring",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Antall historiske meldinger",
|
Title: "Tall på tilhørende meldinger",
|
||||||
SubTitle: "Antall historiske meldinger som sendes med hver forespørsel",
|
SubTitle: "Antall sendte meldinger tilknyttet hver spørring",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Kompressterskel for historiske meldinger",
|
Title: "Terskeverdi for komprimering av historikk",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Når ukomprimerte historiske meldinger overskrider denne verdien, vil de bli komprimert",
|
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Saldoforespørsel",
|
Title: "Saldo for konto",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Brukt denne måneden $${used}, total abonnementsbeløp $${total}`;
|
return `Brukt denne måneden $${used}, abonnement $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Sjekker...",
|
IsChecking: "Sjekker ...",
|
||||||
Check: "Sjekk på nytt",
|
Check: "Sjekk",
|
||||||
NoAccess: "Skriv inn API-nøkkel eller tilgangspassord for å se saldo",
|
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Model",
|
||||||
AccessCode: {
|
|
||||||
Title: "Adgangskode",
|
|
||||||
SubTitle: "Administrator har aktivert kryptert tilgang",
|
|
||||||
Placeholder: "Skriv inn tilgangskoden",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Egendefinert API",
|
|
||||||
SubTitle: "Bruk egendefinerte Azure- eller OpenAI-tjenester",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Modelltilbyder",
|
|
||||||
SubTitle: "Bytt til forskjellige tilbydere",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle:
|
|
||||||
"Bruk egendefinert OpenAI-nøkkel for å omgå passordtilgangsbegrensninger",
|
|
||||||
Placeholder: "OpenAI API-nøkkel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle: "Må inkludere http(s):// utenom standardadresse",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle:
|
|
||||||
"Bruk egendefinert Azure-nøkkel for å omgå passordtilgangsbegrensninger",
|
|
||||||
Placeholder: "Azure API-nøkkel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle: "Eksempel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API-versjon (azure api versjon)",
|
|
||||||
SubTitle: "Velg en spesifikk delversjon",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle:
|
|
||||||
"Bruk egendefinert Anthropic-nøkkel for å omgå passordtilgangsbegrensninger",
|
|
||||||
Placeholder: "Anthropic API-nøkkel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle: "Eksempel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API-versjon (claude api versjon)",
|
|
||||||
SubTitle: "Velg en spesifikk API-versjon",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle: "Hent din API-nøkkel fra Google AI",
|
|
||||||
Placeholder: "Skriv inn din Google AI Studio API-nøkkel",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Endepunktadresse",
|
|
||||||
SubTitle: "Eksempel:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "API-versjon (kun for gemini-pro)",
|
|
||||||
SubTitle: "Velg en spesifikk API-versjon",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google sikkerhetsfiltreringsnivå",
|
|
||||||
SubTitle: "Sett innholdsfiltreringsnivå",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle: "Bruk egendefinert Baidu API-nøkkel",
|
|
||||||
Placeholder: "Baidu API-nøkkel",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Hemmelig nøkkel",
|
|
||||||
SubTitle: "Bruk egendefinert Baidu hemmelig nøkkel",
|
|
||||||
Placeholder: "Baidu hemmelig nøkkel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle:
|
|
||||||
"Støtter ikke egendefinerte konfigurasjoner. Se .env-konfigurasjon.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle: "Bruk egendefinert ByteDance API-nøkkel",
|
|
||||||
Placeholder: "ByteDance API-nøkkel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle: "Eksempel:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-nøkkel",
|
|
||||||
SubTitle: "Bruk egendefinert Alibaba Cloud API-nøkkel",
|
|
||||||
Placeholder: "Alibaba Cloud API-nøkkel",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API-adresse",
|
|
||||||
SubTitle: "Eksempel:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Egendefinert modellnavn",
|
|
||||||
SubTitle: "Legg til egendefinerte modellalternativer, skill med komma",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Modell",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Tilfeldighet (temperature)",
|
Title: "Temperatur",
|
||||||
SubTitle: "Høyere verdi gir mer tilfeldige svar",
|
SubTitle: "Høyere verdi gir mer kreative svar",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Kjerneprøvetaking (top_p)",
|
|
||||||
SubTitle:
|
|
||||||
"Ligner på tilfeldighet, men endre ikke sammen med tilfeldighet",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Maksimalt antall tokens per svar (max_tokens)",
|
Title: "Maks tokens",
|
||||||
SubTitle: "Maksimalt antall tokens brukt i en enkelt interaksjon",
|
SubTitle: "Maksimum lengde på tokens for instrukser og svar",
|
||||||
},
|
|
||||||
PresencePenalty: {
|
|
||||||
Title: "Emnens ferskhet (presence_penalty)",
|
|
||||||
SubTitle:
|
|
||||||
"Høyere verdi gir større sannsynlighet for å utvide til nye emner",
|
|
||||||
},
|
|
||||||
FrequencyPenalty: {
|
|
||||||
Title: "Frekvensstraff (frequency_penalty)",
|
|
||||||
SubTitle:
|
|
||||||
"Høyere verdi gir større sannsynlighet for å redusere gjentakende ord",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Ny samtale",
|
DefaultTopic: "Ny samtale",
|
||||||
BotHello: "Hva kan jeg hjelpe deg med?",
|
BotHello: "Hei! Hva kan jeg hjelpe deg med i dag?",
|
||||||
Error: "Noe gikk galt, prøv igjen senere",
|
Error: "Noe gikk galt, vennligst prøv igjen senere.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Dette er oppsummeringen av historiske samtaler som bakgrunn:" +
|
"Dette er et sammendrag av chatthistorikken mellom AI-en og brukeren som en oppsummering: " +
|
||||||
content,
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
'Bruk fire til fem ord for å returnere en kort oppsummering av temaet, uten forklaring, uten tegnsetting, uten fyllord, uten ekstra tekst, uten fet skrift. Hvis det ikke er noe tema, returner bare "sladder".',
|
"Vennligst lag en fire til fem ords tittel som oppsummerer samtalen vår uten innledning, punktsetting, anførselstegn, punktum, symboler eller tillegg tekst. Fjern innrammende anførselstegn.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Oppsummer samtalen kort som en kontekstuell prompt for fremtidige referanser, hold det innen 200 tegn",
|
"Oppsummer diskusjonen vår kort i 200 ord eller mindre for å bruke som en oppfordring til fremtidig sammenheng.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Kopiert til utklippstavlen",
|
Success: "Kopiert til utklippstavle",
|
||||||
Failed: "Kopiering mislyktes, vennligst gi tillatelse til utklippstavlen",
|
Failed: "Kopiering feilet. Vennligst gi tilgang til utklippstavlen.",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "Innholdet er lastet ned til katalogen din.",
|
|
||||||
Failed: "Nedlasting mislyktes.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Inneholder ${x} forhåndsinnstilte oppfordringer`,
|
Toast: (x: any) => `Med ${x} kontekstuelle instrukser`,
|
||||||
Edit: "Nåværende samtaleinnstillinger",
|
Edit: "Kontekstuelle -og minneinstrukser",
|
||||||
Add: "Legg til en ny samtale",
|
Add: "Legg til",
|
||||||
Clear: "Konteksten er tømt",
|
|
||||||
Revert: "Gjenopprett konteksten",
|
|
||||||
},
|
|
||||||
Plugin: {
|
|
||||||
Name: "Plugin",
|
|
||||||
},
|
|
||||||
FineTuned: {
|
|
||||||
Sysmessage: "Du er en assistent",
|
|
||||||
},
|
|
||||||
Mask: {
|
|
||||||
Name: "Maske",
|
|
||||||
Page: {
|
|
||||||
Title: "Forhåndsdefinerte rollemasker",
|
|
||||||
SubTitle: (count: number) =>
|
|
||||||
`${count} forhåndsdefinerte rolledefinisjoner`,
|
|
||||||
Search: "Søk etter rollemasker",
|
|
||||||
Create: "Opprett ny",
|
|
||||||
},
|
|
||||||
Item: {
|
|
||||||
Info: (count: number) => `Inneholder ${count} forhåndsdefinerte samtaler`,
|
|
||||||
Chat: "Samtale",
|
|
||||||
View: "Vis",
|
|
||||||
Edit: "Rediger",
|
|
||||||
Delete: "Slett",
|
|
||||||
DeleteConfirm: "Bekreft sletting?",
|
|
||||||
},
|
|
||||||
EditModal: {
|
|
||||||
Title: (readonly: boolean) =>
|
|
||||||
`Rediger forhåndsdefinert maske ${readonly ? "(kun lesing)" : ""}`,
|
|
||||||
Download: "Last ned forhåndsdefinert",
|
|
||||||
Clone: "Kopier forhåndsdefinert",
|
|
||||||
},
|
|
||||||
Config: {
|
|
||||||
Avatar: "Rollebilde",
|
|
||||||
Name: "Rolle navn",
|
|
||||||
Sync: {
|
|
||||||
Title: "Bruk globale innstillinger",
|
|
||||||
SubTitle: "Bruker nåværende samtale globale modellinnstillinger",
|
|
||||||
Confirm:
|
|
||||||
"De tilpassede innstillingene for den nåværende samtalen vil bli overskrevet automatisk. Bekreft at du vil bruke globale innstillinger?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Skjul forhåndsdefinerte samtaler",
|
|
||||||
SubTitle:
|
|
||||||
"Når skjult, vil forhåndsdefinerte samtaler ikke vises i chat-grensesnittet",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Del denne masken",
|
|
||||||
SubTitle: "Generer en direkte lenke til denne masken",
|
|
||||||
Action: "Kopier lenke",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NewChat: {
|
|
||||||
Return: "Tilbake",
|
|
||||||
Skip: "Start direkte",
|
|
||||||
NotShow: "Vis ikke igjen",
|
|
||||||
ConfirmNoShow:
|
|
||||||
"Bekreft deaktivering? Du kan aktivere det igjen i innstillingene når som helst.",
|
|
||||||
Title: "Velg en maske",
|
|
||||||
SubTitle: "Start nå, og få tanker som kolliderer med sjelen bak masken",
|
|
||||||
More: "Se alle",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Oppdaget at lenken allerede inneholder tilgangskode. Ønsker du å fylle den ut automatisk?",
|
|
||||||
Settings:
|
|
||||||
"Oppdaget at lenken inneholder forhåndsinnstillinger. Ønsker du å fylle dem ut automatisk?",
|
|
||||||
},
|
|
||||||
|
|
||||||
UI: {
|
|
||||||
Confirm: "Bekreft",
|
|
||||||
Cancel: "Avbryt",
|
|
||||||
Close: "Lukk",
|
|
||||||
Create: "Opprett",
|
|
||||||
Edit: "Rediger",
|
|
||||||
Export: "Eksporter",
|
|
||||||
Import: "Importer",
|
|
||||||
Sync: "Synkroniser",
|
|
||||||
Config: "Konfigurer",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
Model: "Model",
|
||||||
Title: "Bare meldinger etter at konteksten er ryddet vil bli vist",
|
Messages: "Meldingar",
|
||||||
},
|
|
||||||
Model: "Modell",
|
|
||||||
Messages: "Meldinger",
|
|
||||||
Topic: "Emne",
|
Topic: "Emne",
|
||||||
Time: "Tid",
|
Time: "Tid",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -153,12 +153,6 @@ const pt: PartialLocaleType = {
|
|||||||
Title: "Tamanho da Fonte",
|
Title: "Tamanho da Fonte",
|
||||||
SubTitle: "Ajustar o tamanho da fonte do conteúdo do chat",
|
SubTitle: "Ajustar o tamanho da fonte do conteúdo do chat",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "Fonte do Chat",
|
|
||||||
SubTitle:
|
|
||||||
"Fonte do conteúdo do chat, deixe vazio para aplicar a fonte padrão global",
|
|
||||||
Placeholder: "Nome da Fonte",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Inserir Prompts de Sistema",
|
Title: "Inserir Prompts de Sistema",
|
||||||
SubTitle: "Inserir um prompt de sistema global para cada requisição",
|
SubTitle: "Inserir um prompt de sistema global para cada requisição",
|
||||||
|
|||||||
@@ -1,537 +1,229 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import { PartialLocaleType } from "../locales/index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const ru: PartialLocaleType = {
|
const ru: PartialLocaleType = {
|
||||||
WIP: "Скоро...",
|
WIP: "Скоро...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Обнаружен недействительный API-ключ. Пожалуйста, перейдите на страницу [Настройки](/#/settings), чтобы проверить правильность конфигурации API-ключа."
|
"Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
|
||||||
: "Неверный или пустой пароль доступа. Пожалуйста, перейдите на страницу [Вход](/#/auth), чтобы ввести правильный пароль доступа, или на страницу [Настройки](/#/settings), чтобы ввести ваш собственный API-ключ OpenAI.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Требуется пароль",
|
|
||||||
Tips: "Администратор включил проверку пароля. Пожалуйста, введите код доступа ниже",
|
|
||||||
SubTips: "Или введите ваш API-ключ OpenAI или Google",
|
|
||||||
Input: "Введите код доступа здесь",
|
|
||||||
Confirm: "Подтвердить",
|
|
||||||
Later: "Позже",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} бесед`,
|
ChatItemCount: (count: number) => `${count} сообщений`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Всего ${count} бесед`,
|
SubTitle: (count: number) => `${count} сообщений с ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Редактировать сообщение",
|
|
||||||
Topic: {
|
|
||||||
Title: "Тема чата",
|
|
||||||
SubTitle: "Изменить текущую тему чата",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Просмотреть список сообщений",
|
ChatList: "Перейти к списку чатов",
|
||||||
CompressedHistory: "Просмотреть сжатую историю подсказок",
|
CompressedHistory: "Сжатая история памяти",
|
||||||
Export: "Экспортировать чат",
|
Export: "Экспортировать все сообщения в формате Markdown",
|
||||||
Copy: "Копировать",
|
Copy: "Копировать",
|
||||||
Stop: "Остановить",
|
Stop: "Остановить",
|
||||||
Retry: "Повторить",
|
Retry: "Повторить",
|
||||||
Pin: "Закрепить",
|
|
||||||
PinToastContent: "1 беседа закреплена в предустановленных подсказках",
|
|
||||||
PinToastAction: "Просмотреть",
|
|
||||||
Delete: "Удалить",
|
Delete: "Удалить",
|
||||||
Edit: "Редактировать",
|
|
||||||
},
|
|
||||||
Commands: {
|
|
||||||
new: "Новый чат",
|
|
||||||
newm: "Создать чат из маски",
|
|
||||||
next: "Следующий чат",
|
|
||||||
prev: "Предыдущий чат",
|
|
||||||
clear: "Очистить контекст",
|
|
||||||
del: "Удалить чат",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Остановить ответ",
|
|
||||||
ToBottom: "Перейти к последнему",
|
|
||||||
Theme: {
|
|
||||||
auto: "Автоматическая тема",
|
|
||||||
light: "Светлая тема",
|
|
||||||
dark: "Темная тема",
|
|
||||||
},
|
|
||||||
Prompt: "Быстрая команда",
|
|
||||||
Masks: "Все маски",
|
|
||||||
Clear: "Очистить чат",
|
|
||||||
Settings: "Настройки чата",
|
|
||||||
UploadImage: "Загрузить изображение",
|
|
||||||
},
|
},
|
||||||
Rename: "Переименовать чат",
|
Rename: "Переименовать чат",
|
||||||
Typing: "Печатает…",
|
Typing: "Печатает…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} Отправить`;
|
var inputHints = `${submitKey} для отправки сообщения`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter для новой строки";
|
inputHints += ", Shift + Enter для переноса строки";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ для автозаполнения,: для команд";
|
return inputHints + ", / для поиска подсказок";
|
||||||
},
|
},
|
||||||
Send: "Отправить",
|
Send: "Отправить",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Очистить память",
|
Reset: "Сбросить настройки",
|
||||||
SaveAs: "Сохранить как маску",
|
SaveAs: "Сохранить как маску",
|
||||||
},
|
},
|
||||||
IsContext: "Предустановленные подсказки",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Поделиться историей чата",
|
Title: "Все сообщения",
|
||||||
Copy: "Копировать все",
|
Copy: "Копировать все",
|
||||||
Download: "Скачать файл",
|
Download: "Скачать",
|
||||||
Share: "Поделиться в ShareGPT",
|
MessageFromYou: "Сообщение от вас",
|
||||||
MessageFromYou: "Пользователь",
|
MessageFromChatGPT: "Сообщение от ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Формат экспорта",
|
|
||||||
SubTitle: "Можно экспортировать как Markdown текст или PNG изображение",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Включить контекст маски",
|
|
||||||
SubTitle: "Показывать ли контекст маски в сообщениях",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Выбрать",
|
|
||||||
Preview: "Предпросмотр",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Создание скриншота",
|
|
||||||
Modal: "Длительное нажатие или правый клик для сохранения изображения",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Поиск сообщений",
|
|
||||||
All: "Выбрать все",
|
|
||||||
Latest: "Последние сообщения",
|
|
||||||
Clear: "Очистить выбор",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Историческое резюме",
|
Title: "Память",
|
||||||
EmptyContent: "Содержимое чата слишком короткое, чтобы суммировать",
|
EmptyContent: "Пусто.",
|
||||||
Send: "Автоматически сжать историю чата и отправить как контекст",
|
Send: "Отправить память",
|
||||||
Copy: "Копировать резюме",
|
Copy: "Копировать память",
|
||||||
Reset: "[не используется]",
|
Reset: "Сбросить сессию",
|
||||||
ResetConfirm: "Подтвердить очистку исторического резюме?",
|
ResetConfirm:
|
||||||
|
"При сбросе текущая история переписки и историческая память будут удалены. Вы уверены, что хотите сбросить?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Новый чат",
|
NewChat: "Новый чат",
|
||||||
DeleteChat: "Подтвердить удаление выбранного чата?",
|
DeleteChat: "Вы действительно хотите удалить выбранный разговор?",
|
||||||
DeleteToast: "Беседа удалена",
|
DeleteToast: "Чат удален",
|
||||||
Revert: "Отменить",
|
Revert: "Отмена",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Настройки",
|
Title: "Настройки",
|
||||||
SubTitle: "Все параметры настроек",
|
SubTitle: "Все настройки",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Сброс всех настроек",
|
|
||||||
SubTitle: "Сброс всех параметров до значений по умолчанию",
|
|
||||||
Action: "Сбросить сейчас",
|
|
||||||
Confirm: "Подтвердить сброс всех настроек?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Очистить все данные",
|
|
||||||
SubTitle: "Очистить все чаты и данные настроек",
|
|
||||||
Action: "Очистить сейчас",
|
|
||||||
Confirm: "Подтвердить очистку всех чатов и данных настроек?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // ATTENTION: если вы хотите добавить новый перевод, не переводите это значение, оставьте его как `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Все языки",
|
All: "Все языки",
|
||||||
},
|
},
|
||||||
Avatar: "Аватар",
|
Avatar: "Аватар",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Размер шрифта",
|
Title: "Размер шрифта",
|
||||||
SubTitle: "Размер шрифта в чате",
|
SubTitle: "Настроить размер шрифта контента чата",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Шрифт чата",
|
|
||||||
SubTitle:
|
|
||||||
"Шрифт содержимого чата, оставьте пустым для применения глобального шрифта по умолчанию",
|
|
||||||
Placeholder: "Название шрифта",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Вставить системные подсказки",
|
Title: "Вставить системные подсказки",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Принудительно добавлять системную подсказку, имитирующую ChatGPT, в начале каждого запроса",
|
"Принудительно добавить симулированную системную подсказку ChatGPT в начало списка сообщений для каждого запроса",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Предварительная обработка пользовательского ввода",
|
|
||||||
SubTitle:
|
|
||||||
"Последнее сообщение пользователя будет подставлено в этот шаблон",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Текущая версия: ${x}`,
|
Version: (x: string) => `Версия: ${x}`,
|
||||||
IsLatest: "Установлена последняя версия",
|
IsLatest: "Последняя версия",
|
||||||
CheckUpdate: "Проверить обновления",
|
CheckUpdate: "Проверить обновление",
|
||||||
IsChecking: "Проверка обновлений...",
|
IsChecking: "Проверка обновления...",
|
||||||
FoundUpdate: (x: string) => `Найдено новое обновление: ${x}`,
|
FoundUpdate: (x: string) => `Найдена новая версия: ${x}`,
|
||||||
GoToUpdate: "Перейти к обновлению",
|
GoToUpdate: "Обновить",
|
||||||
},
|
},
|
||||||
SendKey: "Кнопка отправки",
|
SendKey: "Клавиша отправки",
|
||||||
Theme: "Тема",
|
Theme: "Тема",
|
||||||
TightBorder: "Режим без границ",
|
TightBorder: "Узкая граница",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Предварительный просмотр пузырьков",
|
Title: "Отправить предпросмотр",
|
||||||
SubTitle:
|
SubTitle: "Предварительный просмотр markdown в пузыре",
|
||||||
"Просмотр содержимого Markdown в пузырьках предварительного просмотра",
|
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Автоматическое создание заголовка",
|
|
||||||
SubTitle: "Создание подходящего заголовка на основе содержания беседы",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Облачные данные",
|
|
||||||
NotSyncYet: "Синхронизация еще не проводилась",
|
|
||||||
Success: "Синхронизация успешна",
|
|
||||||
Fail: "Не удалось синхронизировать",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Настройки облачной синхронизации",
|
|
||||||
Check: "Проверить доступность",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Тип синхронизации",
|
|
||||||
SubTitle: "Выберите предпочитаемый сервер синхронизации",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Включить прокси",
|
|
||||||
SubTitle:
|
|
||||||
"При синхронизации в браузере необходимо включить прокси для предотвращения ограничений кросс-домена",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Адрес прокси",
|
|
||||||
SubTitle: "Только для встроенного прокси в проекте",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV адрес",
|
|
||||||
UserName: "Имя пользователя",
|
|
||||||
Password: "Пароль",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST Url",
|
|
||||||
UserName: "Имя резервной копии",
|
|
||||||
Password: "UpStash Redis REST Token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Локальные данные",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} бесед, ${overview.message} сообщений, ${overview.prompt} подсказок, ${overview.mask} масок`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Не удалось импортировать",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Стартовая страница масок",
|
Title: "Экран заставки маски",
|
||||||
SubTitle:
|
SubTitle: "Показывать экран заставки маски перед началом нового чата",
|
||||||
"При создании нового чата отображать стартовую страницу масок",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Скрыть встроенные маски",
|
|
||||||
SubTitle: "Скрыть встроенные маски во всех списках масок",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Отключить автозаполнение подсказок",
|
Title: "Отключить автозаполнение",
|
||||||
SubTitle: "Введите / в начале строки для активации автозаполнения",
|
SubTitle: "Ввод / для запуска автозаполнения",
|
||||||
},
|
},
|
||||||
List: "Список пользовательских подсказок",
|
List: "Список подсказок",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Встроенные ${builtin}, пользовательские ${custom}`,
|
`${builtin} встроенных, ${custom} пользовательских`,
|
||||||
Edit: "Редактировать",
|
Edit: "Редактировать",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Список подсказок",
|
Title: "Список подсказок",
|
||||||
Add: "Создать",
|
Add: "Добавить",
|
||||||
Search: "Поиск подсказок",
|
Search: "Поиск подсказок",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Редактировать подсказки",
|
Title: "Редактировать подсказку",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Количество истории сообщений",
|
Title: "Количество прикрепляемых сообщений",
|
||||||
SubTitle: "Количество историй сообщений, отправляемых с каждым запросом",
|
SubTitle:
|
||||||
|
"Количество отправляемых сообщений, прикрепляемых к каждому запросу",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Порог сжатия длины истории сообщений",
|
Title: "Порог сжатия истории",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Когда не сжатая история сообщений превышает это значение, происходит сжатие",
|
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Проверка баланса",
|
Title: "Баланс аккаунта",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Использовано в этом месяце $${used}, всего по подписке $${total}`;
|
return `Использовано в этом месяце $${used}, подписка $${total}`;
|
||||||
},
|
|
||||||
IsChecking: "Проверка…",
|
|
||||||
Check: "Проверить снова",
|
|
||||||
NoAccess: "Введите API-ключ или пароль доступа для просмотра баланса",
|
|
||||||
},
|
|
||||||
|
|
||||||
Access: {
|
|
||||||
AccessCode: {
|
|
||||||
Title: "Пароль доступа",
|
|
||||||
SubTitle: "Администратор включил защиту паролем",
|
|
||||||
Placeholder: "Введите пароль доступа",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Пользовательский интерфейс",
|
|
||||||
SubTitle: "Использовать ли пользовательский Azure или OpenAI сервис",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Провайдер модели",
|
|
||||||
SubTitle: "Переключиться на другого провайдера",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-ключ",
|
|
||||||
SubTitle:
|
|
||||||
"Использовать пользовательский OpenAI-ключ для обхода ограничений пароля",
|
|
||||||
Placeholder: "OpenAI API-ключ",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Кроме адреса по умолчанию, должен содержать http(s)://",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Ключ интерфейса",
|
|
||||||
SubTitle:
|
|
||||||
"Использовать пользовательский Azure-ключ для обхода ограничений пароля",
|
|
||||||
Placeholder: "Azure API-ключ",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Пример:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Версия интерфейса (azure api version)",
|
|
||||||
SubTitle: "Выберите конкретную версию",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Ключ интерфейса",
|
|
||||||
SubTitle:
|
|
||||||
"Использовать пользовательский Anthropic-ключ для обхода ограничений пароля",
|
|
||||||
Placeholder: "Anthropic API-ключ",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Пример:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Версия интерфейса (claude api version)",
|
|
||||||
SubTitle: "Выберите конкретную версию API",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-ключ",
|
|
||||||
SubTitle: "Получите ваш API-ключ Google AI",
|
|
||||||
Placeholder: "Введите ваш API-ключ Google AI Studio",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Пример:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Версия API (только для gemini-pro)",
|
|
||||||
SubTitle: "Выберите конкретную версию API",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Уровень фильтрации Google",
|
|
||||||
SubTitle: "Настроить уровень фильтрации контента",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API-ключ",
|
|
||||||
SubTitle: "Использовать пользовательский Baidu API-ключ",
|
|
||||||
Placeholder: "Baidu API-ключ",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Секретный ключ",
|
|
||||||
SubTitle: "Использовать пользовательский Baidu Secret Key",
|
|
||||||
Placeholder: "Baidu Secret Key",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle:
|
|
||||||
"Не поддерживает пользовательскую настройку, перейдите в .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Ключ интерфейса",
|
|
||||||
SubTitle: "Использовать пользовательский ByteDance API-ключ",
|
|
||||||
Placeholder: "ByteDance API-ключ",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Пример:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Ключ интерфейса",
|
|
||||||
SubTitle: "Использовать пользовательский Alibaba Cloud API-ключ",
|
|
||||||
Placeholder: "Alibaba Cloud API-ключ",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Адрес интерфейса",
|
|
||||||
SubTitle: "Пример:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Название пользовательской модели",
|
|
||||||
SubTitle:
|
|
||||||
"Добавьте варианты пользовательских моделей, разделяя запятыми",
|
|
||||||
},
|
},
|
||||||
|
IsChecking: "Проверка...",
|
||||||
|
Check: "Проверить",
|
||||||
|
NoAccess: "Введите API ключ, чтобы проверить баланс",
|
||||||
},
|
},
|
||||||
|
|
||||||
Model: "Модель",
|
Model: "Модель",
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Случайность (temperature)",
|
Title: "Температура",
|
||||||
SubTitle: "Чем больше значение, тем более случайные ответы",
|
SubTitle: "Чем выше значение, тем более случайный вывод",
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Ядро выборки (top_p)",
|
|
||||||
SubTitle: "Похожие на случайность, но не изменяйте вместе с случайностью",
|
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Ограничение на количество токенов за один раз (max_tokens)",
|
Title: "Максимальное количество токенов",
|
||||||
SubTitle: "Максимальное количество токенов на одно взаимодействие",
|
SubTitle: "Максимальная длина вводных и генерируемых токенов",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Наказание за новизну тем (presence_penalty)",
|
Title: "Штраф за повторения",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Чем больше значение, тем выше вероятность расширения на новые темы",
|
"Чем выше значение, тем больше вероятность общения на новые темы",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Наказание за частоту (frequency_penalty)",
|
Title: "Штраф за частоту",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Чем больше значение, тем выше вероятность уменьшения повторяющихся слов",
|
"Большее значение снижает вероятность повторения одной и той же строки",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Новый чат",
|
DefaultTopic: "Новый разговор",
|
||||||
BotHello: "Чем могу помочь?",
|
BotHello: "Здравствуйте! Как я могу вам помочь сегодня?",
|
||||||
Error: "Произошла ошибка, попробуйте позже",
|
Error: "Что-то пошло не так. Пожалуйста, попробуйте еще раз позже.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Это резюме истории чата как предыстория: " + content,
|
"Это краткое содержание истории чата между ИИ и пользователем: " +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Укажите краткую тему этого сообщения в четырех-пяти словах, без объяснений, знаков препинания, междометий, лишнего текста или выделения. Если темы нет, просто напишите 'Болтовня'",
|
"Пожалуйста, создайте заголовок из четырех или пяти слов, который кратко описывает нашу беседу, без введения, знаков пунктуации, кавычек, точек, символов или дополнительного текста. Удалите кавычки.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Кратко подведите итоги содержимого беседы для использования в качестве последующего контекстного запроса, не более 200 слов",
|
"Кратко изложите нашу дискуссию в 200 словах или менее для использования в будущем контексте.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Скопировано в буфер обмена",
|
Success: "Скопировано в буфер обмена",
|
||||||
Failed: "Не удалось скопировать, предоставьте доступ к буферу обмена",
|
Failed:
|
||||||
},
|
"Не удалось скопировать, пожалуйста, предоставьте разрешение на доступ к буферу обмена",
|
||||||
Download: {
|
|
||||||
Success: "Содержимое успешно загружено в вашу директорию.",
|
|
||||||
Failed: "Не удалось загрузить.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Содержит ${x} предустановленных подсказок`,
|
Toast: (x: any) => `С ${x} контекстными подсказками`,
|
||||||
Edit: "Текущие настройки чата",
|
Edit: "Контекстные и памятные подсказки",
|
||||||
Add: "Добавить новый чат",
|
Add: "Добавить подсказку",
|
||||||
Clear: "Контекст очищен",
|
|
||||||
Revert: "Восстановить контекст",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Плагины",
|
Name: "Плагин",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Вы - помощник",
|
Sysmessage: "Вы - ассистент, который",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Маска",
|
Name: "Маска",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Предустановленные роли",
|
Title: "Шаблон подсказки",
|
||||||
SubTitle: (count: number) => `${count} предустановленных ролей`,
|
SubTitle: (count: number) => `${count} шаблонов подсказок`,
|
||||||
Search: "Поиск ролей",
|
Search: "Поиск шаблонов",
|
||||||
Create: "Создать",
|
Create: "Создать",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Содержит ${count} предустановленных диалогов`,
|
Info: (count: number) => `${count} подсказок`,
|
||||||
Chat: "Диалог",
|
Chat: "Чат",
|
||||||
View: "Просмотреть",
|
View: "Просмотр",
|
||||||
Edit: "Редактировать",
|
Edit: "Редактировать",
|
||||||
Delete: "Удалить",
|
Delete: "Удалить",
|
||||||
DeleteConfirm: "Подтвердить удаление?",
|
DeleteConfirm: "Подтвердить удаление?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Редактирование предустановленной маски ${
|
`Редактирование шаблона подсказки ${
|
||||||
readonly ? "(только для чтения)" : ""
|
readonly ? "(только для чтения)" : ""
|
||||||
}`,
|
}`,
|
||||||
Download: "Скачать предустановку",
|
Download: "Скачать",
|
||||||
Clone: "Клонировать предустановку",
|
Clone: "Клонировать",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Аватар роли",
|
Avatar: "Аватар бота",
|
||||||
Name: "Название роли",
|
Name: "Имя бота",
|
||||||
Sync: {
|
|
||||||
Title: "Использовать глобальные настройки",
|
|
||||||
SubTitle:
|
|
||||||
"Будет ли текущий чат использовать глобальные настройки модели",
|
|
||||||
Confirm:
|
|
||||||
"Пользовательские настройки текущего чата будут автоматически заменены, подтвердите активацию глобальных настроек?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Скрыть предустановленные диалоги",
|
|
||||||
SubTitle:
|
|
||||||
"После скрытия предустановленные диалоги не будут отображаться в чате",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Поделиться этой маской",
|
|
||||||
SubTitle: "Создать прямую ссылку на эту маску",
|
|
||||||
Action: "Скопировать ссылку",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Вернуться",
|
Return: "Вернуться",
|
||||||
Skip: "Начать сразу",
|
Skip: "Пропустить",
|
||||||
NotShow: "Больше не показывать",
|
|
||||||
ConfirmNoShow:
|
|
||||||
"Подтвердить отключение? После отключения вы всегда сможете включить его снова в настройках.",
|
|
||||||
Title: "Выберите маску",
|
Title: "Выберите маску",
|
||||||
SubTitle: "Начните сейчас, столкнитесь с мыслями за маской",
|
SubTitle: "Общайтесь с душой за маской",
|
||||||
More: "Показать все",
|
More: "Найти еще",
|
||||||
},
|
NotShow: "Не показывать снова",
|
||||||
|
ConfirmNoShow:
|
||||||
URLCommand: {
|
"Подтвердите отключение? Вы можете включить это позже в настройках.",
|
||||||
Code: "Обнаружен код доступа в ссылке, автоматически заполнить?",
|
|
||||||
Settings:
|
|
||||||
"Обнаружены предустановленные настройки в ссылке, автоматически заполнить?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
@@ -540,15 +232,8 @@ const ru: PartialLocaleType = {
|
|||||||
Close: "Закрыть",
|
Close: "Закрыть",
|
||||||
Create: "Создать",
|
Create: "Создать",
|
||||||
Edit: "Редактировать",
|
Edit: "Редактировать",
|
||||||
Export: "Экспортировать",
|
|
||||||
Import: "Импортировать",
|
|
||||||
Sync: "Синхронизировать",
|
|
||||||
Config: "Настройки",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Только сообщения после очистки контекста будут отображаться",
|
|
||||||
},
|
|
||||||
Model: "Модель",
|
Model: "Модель",
|
||||||
Messages: "Сообщения",
|
Messages: "Сообщения",
|
||||||
Topic: "Тема",
|
Topic: "Тема",
|
||||||
|
|||||||
@@ -155,12 +155,6 @@ const sk: PartialLocaleType = {
|
|||||||
Title: "Veľkosť písma",
|
Title: "Veľkosť písma",
|
||||||
SubTitle: "Nastaviť veľkosť písma obsahu chatu",
|
SubTitle: "Nastaviť veľkosť písma obsahu chatu",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "Chatové Písmo",
|
|
||||||
SubTitle:
|
|
||||||
"Písmo obsahu chatu, ponechajte prázdne pre použitie globálneho predvoleného písma",
|
|
||||||
Placeholder: "Názov Písma",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Vložiť systémové výzvy",
|
Title: "Vložiť systémové výzvy",
|
||||||
SubTitle: "Vložiť globálnu systémovú výzvu pre každú požiadavku",
|
SubTitle: "Vložiť globálnu systémovú výzvu pre každú požiadavku",
|
||||||
|
|||||||
@@ -1,552 +1,236 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const tr: PartialLocaleType = {
|
const tr: PartialLocaleType = {
|
||||||
WIP: "Çalışma devam ediyor...",
|
WIP: "Çalışma devam ediyor...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Geçersiz API Anahtarı tespit edildi, lütfen API Anahtarını doğru şekilde yapılandırmak için [Ayarlar](/#/settings) sayfasına gidin."
|
"Yetkisiz erişim, lütfen erişim kodunu ayarlar [sayfasından](/#/auth) giriniz.",
|
||||||
: "Erişim şifresi yanlış veya boş, lütfen doğru erişim şifresini girmek için [Giriş](/#/auth) sayfasına gidin veya kendi OpenAI API Anahtarınızı [Ayarlar](/#/settings) sayfasına girin.",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Şifre Gerekli",
|
|
||||||
Tips: "Yönetici şifre doğrulamasını etkinleştirdi, lütfen aşağıya erişim kodunu girin",
|
|
||||||
SubTips: "Veya OpenAI veya Google API anahtarınızı girin",
|
|
||||||
Input: "Erişim kodunu buraya girin",
|
|
||||||
Confirm: "Onayla",
|
|
||||||
Later: "Sonra",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} konuşma`,
|
ChatItemCount: (count: number) => `${count} mesaj`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Toplam ${count} konuşma`,
|
SubTitle: (count: number) => `ChatGPT tarafından ${count} mesaj`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Mesaj Kayıtlarını Düzenle",
|
|
||||||
Topic: {
|
|
||||||
Title: "Sohbet Konusu",
|
|
||||||
SubTitle: "Geçerli sohbet konusunu değiştir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Mesaj listesine bak",
|
ChatList: "Sohbet Listesine Git",
|
||||||
CompressedHistory: "Sıkıştırılmış geçmişi gör",
|
CompressedHistory: "Sıkıştırılmış Geçmiş Bellek Komutu",
|
||||||
Export: "Sohbet kayıtlarını dışa aktar",
|
Export: "Tüm Mesajları Markdown Olarak Dışa Aktar",
|
||||||
Copy: "Kopyala",
|
Copy: "Kopyala",
|
||||||
Stop: "Durdur",
|
Stop: "Durdur",
|
||||||
Retry: "Yeniden dene",
|
Retry: "Tekrar Dene",
|
||||||
Pin: "Sabitlenmiş",
|
Delete: "Delete",
|
||||||
PinToastContent: "1 konuşma varsayılan ifadeye sabitlendi",
|
|
||||||
PinToastAction: "Görünüm",
|
|
||||||
Delete: "Sil",
|
|
||||||
Edit: "Düzenle",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Sohbeti Yeniden Adlandır",
|
||||||
new: "Yeni sohbet",
|
|
||||||
newm: "Maske ile yeni sohbet oluştur",
|
|
||||||
next: "Sonraki sohbet",
|
|
||||||
prev: "Önceki sohbet",
|
|
||||||
clear: "Konteksti temizle",
|
|
||||||
del: "Sohbeti sil",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Yanıtı durdur",
|
|
||||||
ToBottom: "En alta git",
|
|
||||||
Theme: {
|
|
||||||
auto: "Otomatik tema",
|
|
||||||
light: "Açık mod",
|
|
||||||
dark: "Koyu mod",
|
|
||||||
},
|
|
||||||
Prompt: "Kısayol komutu",
|
|
||||||
Masks: "Tüm maskeler",
|
|
||||||
Clear: "Sohbeti temizle",
|
|
||||||
Settings: "Sohbet ayarları",
|
|
||||||
UploadImage: "Resim yükle",
|
|
||||||
},
|
|
||||||
Rename: "Sohbeti yeniden adlandır",
|
|
||||||
Typing: "Yazıyor…",
|
Typing: "Yazıyor…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} gönder`;
|
var inputHints = `Göndermek için ${submitKey}`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += " Shift + Enter satır sonu için";
|
inputHints += ", kaydırmak için Shift + Enter";
|
||||||
}
|
}
|
||||||
return inputHints + " / tamamlama için, : komutlar için";
|
return inputHints + ", komutları aramak için / (eğik çizgi)";
|
||||||
},
|
},
|
||||||
Send: "Gönder",
|
Send: "Gönder",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Hafızayı temizle",
|
Reset: "Reset to Default",
|
||||||
SaveAs: "Maske olarak kaydet",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
IsContext: "Varsayılan ifade",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Sohbet kayıtlarını paylaş",
|
Title: "Tüm Mesajlar",
|
||||||
Copy: "Hepsini kopyala",
|
Copy: "Tümünü Kopyala",
|
||||||
Download: "Dosyayı indir",
|
Download: "İndir",
|
||||||
Share: "ShareGPT'ye paylaş",
|
MessageFromYou: "Sizin Mesajınız",
|
||||||
MessageFromYou: "Kullanıcı",
|
MessageFromChatGPT: "ChatGPT'nin Mesajı",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Dışa aktarma formatı",
|
|
||||||
SubTitle: "Markdown metni veya PNG resmi olarak dışa aktarabilirsiniz",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Maske bağlamını dahil et",
|
|
||||||
SubTitle: "Mesajlarda maske bağlamını göstermek ister misiniz",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Seç",
|
|
||||||
Preview: "Önizleme",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Ekran görüntüsü oluşturuluyor",
|
|
||||||
Modal: "Resmi uzun basın veya sağ tıklayın ve kaydedin",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Mesajları ara",
|
|
||||||
All: "Hepsini seç",
|
|
||||||
Latest: "Son birkaç mesaj",
|
|
||||||
Clear: "Seçimi temizle",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Geçmiş Özeti",
|
Title: "Bellek Komutları",
|
||||||
EmptyContent: "Sohbet içeriği çok kısa, özetleme gerek yok",
|
EmptyContent: "Henüz değil.",
|
||||||
Send: "Sohbet kayıtlarını otomatik olarak sıkıştır ve bağlam olarak gönder",
|
Send: "Belleği Gönder",
|
||||||
Copy: "Özeti kopyala",
|
Copy: "Belleği Kopyala",
|
||||||
Reset: "[kullanılmadı]",
|
Reset: "Oturumu Sıfırla",
|
||||||
ResetConfirm: "Geçmiş özetini temizlemek istediğinize emin misiniz?",
|
ResetConfirm:
|
||||||
|
"Sıfırlama, geçerli görüşme geçmişini ve geçmiş belleği siler. Sıfırlamak istediğinizden emin misiniz?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Yeni sohbet",
|
NewChat: "Yeni Sohbet",
|
||||||
DeleteChat: "Seçilen sohbeti silmek istediğinize emin misiniz?",
|
DeleteChat: "Seçili sohbeti silmeyi onaylıyor musunuz?",
|
||||||
DeleteToast: "Sohbet silindi",
|
DeleteToast: "Sohbet Silindi",
|
||||||
Revert: "Geri al",
|
Revert: "Geri Al",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Ayarlar",
|
Title: "Ayarlar",
|
||||||
SubTitle: "Tüm ayar seçenekleri",
|
SubTitle: "Tüm Ayarlar",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Tüm ayarları sıfırla",
|
|
||||||
SubTitle: "Tüm ayarları varsayılan değerlere sıfırla",
|
|
||||||
Action: "Hemen sıfırla",
|
|
||||||
Confirm: "Tüm ayarları sıfırlamak istediğinizden emin misiniz?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Tüm verileri temizle",
|
|
||||||
SubTitle: "Tüm sohbet ve ayar verilerini temizle",
|
|
||||||
Action: "Hemen temizle",
|
|
||||||
Confirm:
|
|
||||||
"Tüm sohbet ve ayar verilerini temizlemek istediğinizden emin misiniz?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // Dikkat: yeni bir çeviri eklemek isterseniz, bu değeri çevirmeyin, `Language` olarak bırakın
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Tüm diller",
|
All: "Tüm Diller",
|
||||||
},
|
},
|
||||||
Avatar: "Profil Resmi",
|
Avatar: "Avatar",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Yazı Boyutu",
|
Title: "Yazı Boyutu",
|
||||||
SubTitle: "Sohbet içeriğinin yazı boyutu",
|
SubTitle: "Sohbet içeriğinin yazı boyutunu ayarlayın",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Sohbet Yazı Tipi",
|
|
||||||
SubTitle:
|
|
||||||
"Sohbet içeriğinin yazı tipi, boş bırakıldığında küresel varsayılan yazı tipi uygulanır",
|
|
||||||
Placeholder: "Yazı Tipi Adı",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Sistem Seviyesi İpucu Enjeksiyonu",
|
Title: "Sistem İpucu Ekleyin",
|
||||||
SubTitle: "Her isteğin başına ChatGPT benzeri bir sistem ipucu ekle",
|
SubTitle:
|
||||||
|
"Her istek için ileti listesinin başına simüle edilmiş bir ChatGPT sistem ipucu ekleyin",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Kullanıcı Girdisi Ön İşleme",
|
|
||||||
SubTitle: "Kullanıcının en son mesajı bu şablona doldurulur",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Mevcut sürüm: ${x}`,
|
Version: (x: string) => `Sürüm: ${x}`,
|
||||||
IsLatest: "En son sürüm",
|
IsLatest: "En son sürüm",
|
||||||
CheckUpdate: "Güncellemeleri kontrol et",
|
CheckUpdate: "Güncellemeyi Kontrol Et",
|
||||||
IsChecking: "Güncellemeler kontrol ediliyor...",
|
IsChecking: "Güncelleme kontrol ediliyor...",
|
||||||
FoundUpdate: (x: string) => `Yeni sürüm bulundu: ${x}`,
|
FoundUpdate: (x: string) => `Yeni sürüm bulundu: ${x}`,
|
||||||
GoToUpdate: "Güncellemeye git",
|
GoToUpdate: "Güncelle",
|
||||||
},
|
},
|
||||||
SendKey: "Gönderme Tuşu",
|
SendKey: "Gönder Tuşu",
|
||||||
Theme: "Tema",
|
Theme: "Tema",
|
||||||
TightBorder: "Sınır Yok Modu",
|
TightBorder: "Tam Ekran",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Önizleme Balonu",
|
Title: "Mesaj Önizleme Balonu",
|
||||||
SubTitle: "Markdown içeriğini önizleme balonunda görüntüle",
|
SubTitle: "Preview markdown in bubble",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Başlığı Otomatik Oluştur",
|
|
||||||
SubTitle: "Sohbet içeriğine göre uygun başlık oluştur",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Bulut Verisi",
|
|
||||||
NotSyncYet: "Henüz senkronize edilmedi",
|
|
||||||
Success: "Senkronizasyon başarılı",
|
|
||||||
Fail: "Senkronizasyon başarısız",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Bulut Senkronizasyonu Yapılandır",
|
|
||||||
Check: "Kullanılabilirliği kontrol et",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Senkronizasyon Türü",
|
|
||||||
SubTitle: "Tercih ettiğiniz senkronizasyon sunucusunu seçin",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Proxy'yi Etkinleştir",
|
|
||||||
SubTitle:
|
|
||||||
"Tarayıcıda senkronize ederken proxy'yi etkinleştirin, aksi takdirde çapraz kaynak kısıtlamalarıyla karşılaşabilirsiniz",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Proxy Adresi",
|
|
||||||
SubTitle: "Sadece bu projeye ait çapraz kaynak proxy için",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "WebDAV Adresi",
|
|
||||||
UserName: "Kullanıcı Adı",
|
|
||||||
Password: "Şifre",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "UpStash Redis REST Url",
|
|
||||||
UserName: "Yedekleme Adı",
|
|
||||||
Password: "UpStash Redis REST Token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Yerel Veri",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} konuşma, ${overview.message} mesaj, ${overview.prompt} ipucu, ${overview.mask} maske`;
|
|
||||||
},
|
|
||||||
ImportFailed: "İçeri aktarma başarısız",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Maske Başlangıç Sayfası",
|
Title: "Mask Splash Screen",
|
||||||
SubTitle:
|
SubTitle: "Show a mask splash screen before starting new chat",
|
||||||
"Yeni sohbet başlatıldığında maske başlangıç sayfasını göster",
|
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Yerleşik Maskeleri Gizle",
|
|
||||||
SubTitle: "Tüm maskeler listesindeki yerleşik maskeleri gizle",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "İpucu Tamamlamayı Devre Dışı Bırak",
|
Title: "Otomatik tamamlamayı devre dışı bırak",
|
||||||
SubTitle:
|
SubTitle: "Otomatik tamamlamayı kullanmak için / (eğik çizgi) girin",
|
||||||
"Giriş kutusunun başına / yazarak otomatik tamamlamayı tetikle",
|
|
||||||
},
|
},
|
||||||
List: "Özelleştirilmiş İpucu Listesi",
|
List: "Komut Listesi",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Yerleşik ${builtin} tane, kullanıcı tanımlı ${custom} tane`,
|
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
||||||
Edit: "Düzenle",
|
Edit: "Düzenle",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "İpucu Listesi",
|
Title: "Prompt List",
|
||||||
Add: "Yeni Ekle",
|
Add: "Add One",
|
||||||
Search: "İpucu Ara",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "İpucu Düzenle",
|
Title: "Edit Prompt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Ekli Geçmiş Mesaj Sayısı",
|
Title: "Ekli Mesaj Sayısı",
|
||||||
SubTitle: "Her istekte taşınan geçmiş mesaj sayısı",
|
SubTitle: "İstek başına ekli gönderilen mesaj sayısı",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Geçmiş Mesaj Uzunluğu Sıkıştırma Eşiği",
|
Title: "Geçmiş Sıkıştırma Eşiği",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Sıkıştırılmamış geçmiş mesaj bu değeri aştığında sıkıştırma yapılır",
|
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Bakiye Sorgulama",
|
Title: "Hesap Bakiyesi",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Bu ay kullanılan $${used}, toplam abonelik ücreti $${total}`;
|
return `Bu ay kullanılan $${used}, abonelik $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Kontrol ediliyor…",
|
IsChecking: "Kontrol ediliyor...",
|
||||||
Check: "Yeniden kontrol et",
|
Check: "Tekrar Kontrol Et",
|
||||||
NoAccess:
|
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
|
||||||
"Bakiye görüntülemek için API Anahtarı veya erişim şifresi girin",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Model",
|
||||||
AccessCode: {
|
|
||||||
Title: "Erişim Şifresi",
|
|
||||||
SubTitle: "Yönetici şifreli erişimi etkinleştirdi",
|
|
||||||
Placeholder: "Erişim şifrenizi girin",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Özelleştirilmiş API",
|
|
||||||
SubTitle:
|
|
||||||
"Özelleştirilmiş Azure veya OpenAI hizmeti kullanmak ister misiniz?",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Model Sağlayıcısı",
|
|
||||||
SubTitle: "Farklı sağlayıcılara geçiş yapın",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle:
|
|
||||||
"Özelleştirilmiş OpenAI Anahtarı kullanarak şifreli erişim kısıtlamalarını atlayın",
|
|
||||||
Placeholder: "OpenAI API Anahtarı",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Varsayılan adres dışında, http(s):// içermelidir",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle:
|
|
||||||
"Özelleştirilmiş Azure Anahtarı kullanarak şifreli erişim kısıtlamalarını atlayın",
|
|
||||||
Placeholder: "Azure API Anahtarı",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Örnek:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API Versiyonu (azure api version)",
|
|
||||||
SubTitle: "Belirli bir versiyonu seçin",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle:
|
|
||||||
"Özelleştirilmiş Anthropic Anahtarı kullanarak şifreli erişim kısıtlamalarını atlayın",
|
|
||||||
Placeholder: "Anthropic API Anahtarı",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Örnek:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "API Versiyonu (claude api version)",
|
|
||||||
SubTitle: "Belirli bir API versiyonunu seçin",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle: "Google AI'den API Anahtarınızı alın",
|
|
||||||
Placeholder: "Google AI Studio API Anahtarınızı girin",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Uç Nokta Adresi",
|
|
||||||
SubTitle: "Örnek:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "API Versiyonu (sadece gemini-pro)",
|
|
||||||
SubTitle: "Belirli bir API versiyonunu seçin",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Google Güvenlik Filtreleme Seviyesi",
|
|
||||||
SubTitle: "İçerik filtreleme seviyesini ayarlayın",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle: "Özelleştirilmiş Baidu API Anahtarı kullanın",
|
|
||||||
Placeholder: "Baidu API Anahtarı",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Anahtarı",
|
|
||||||
SubTitle: "Özelleştirilmiş Baidu Secret Anahtarı kullanın",
|
|
||||||
Placeholder: "Baidu Secret Anahtarı",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Özelleştirilmiş yapılandırma için .env'ye gidin",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle: "Özelleştirilmiş ByteDance API Anahtarı kullanın",
|
|
||||||
Placeholder: "ByteDance API Anahtarı",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Örnek:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Anahtarı",
|
|
||||||
SubTitle: "Özelleştirilmiş Alibaba Cloud API Anahtarı kullanın",
|
|
||||||
Placeholder: "Alibaba Cloud API Anahtarı",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "API Adresi",
|
|
||||||
SubTitle: "Örnek:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Özelleştirilmiş Model Adı",
|
|
||||||
SubTitle:
|
|
||||||
"Özelleştirilmiş model seçenekleri ekleyin, İngilizce virgül ile ayırın",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Model (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Rastgelelik (temperature)",
|
Title: "Gerçeklik",
|
||||||
SubTitle: "Değer arttıkça yanıt daha rastgele olur",
|
|
||||||
},
|
|
||||||
TopP: {
|
|
||||||
Title: "Nükleer Örnekleme (top_p)",
|
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Rastgeleliğe benzer, ancak rastgelelik ile birlikte değiştirmeyin",
|
"Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Tek Yanıt Limiti (max_tokens)",
|
Title: "Maksimum Belirteç",
|
||||||
SubTitle: "Tek etkileşimde kullanılan maksimum Token sayısı",
|
SubTitle:
|
||||||
|
"Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Konu Tazeliği (presence_penalty)",
|
Title: "Varlık Cezası",
|
||||||
SubTitle: "Değer arttıkça, yeni konulara geçiş olasılığı artar",
|
SubTitle:
|
||||||
|
"Daha büyük bir değer, yeni konular hakkında konuşma olasılığını artırır",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Frekans Cezası (frequency_penalty)",
|
Title: "Frekans Cezası",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Değer arttıkça, tekrar eden kelimelerin azalması olasılığı artar",
|
"Aynı satırı tekrar etme olasılığını azaltan daha büyük bir değer",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Yeni Sohbet",
|
DefaultTopic: "Yeni Konuşma",
|
||||||
BotHello: "Size nasıl yardımcı olabilirim?",
|
BotHello: "Merhaba! Size bugün nasıl yardımcı olabilirim?",
|
||||||
Error: "Bir hata oluştu, lütfen daha sonra tekrar deneyin",
|
Error: "Bir şeyler yanlış gitti. Lütfen daha sonra tekrar deneyiniz.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) => "Bu, geçmiş sohbetin özeti: " + content,
|
History: (content: string) =>
|
||||||
|
"Bu, yapay zeka ile kullanıcı arasındaki sohbet geçmişinin bir özetidir: " +
|
||||||
|
content,
|
||||||
Topic:
|
Topic:
|
||||||
"Bu cümlenin dört ila beş kelimelik kısa başlığını doğrudan verin, açıklama yapmayın, noktalama işareti, duygu kelimesi veya fazla metin eklemeyin, kalın yapmayın. Başlık yoksa, doğrudan 'Sohbet' yanıtını verin.",
|
"Lütfen herhangi bir giriş, noktalama işareti, tırnak işareti, nokta, sembol veya ek metin olmadan konuşmamızı özetleyen dört ila beş kelimelik bir başlık oluşturun. Çevreleyen tırnak işaretlerini kaldırın.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Sohbet içeriğini kısaca özetleyin, bu özet sonraki bağlam ipucu olarak kullanılacaktır, 200 kelime içinde tutun",
|
"Gelecekteki bağlam için bir bilgi istemi olarak kullanmak üzere tartışmamızı en fazla 200 kelimeyle özetleyin.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Panoya yazıldı",
|
Success: "Panoya kopyalandı",
|
||||||
Failed: "Kopyalama başarısız, lütfen panoya erişim izni verin",
|
Failed: "Kopyalama başarısız oldu, lütfen panoya erişim izni verin",
|
||||||
},
|
|
||||||
Download: {
|
|
||||||
Success: "İçerik dizininize indirildi.",
|
|
||||||
Failed: "İndirme başarısız.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `${x} tane önceden tanımlı ipucu içeriyor`,
|
Toast: (x: any) => `${x} bağlamsal bellek komutu`,
|
||||||
Edit: "Mevcut sohbet ayarları",
|
Edit: "Bağlamsal ve Bellek Komutları",
|
||||||
Add: "Yeni bir sohbet ekle",
|
Add: "Yeni Ekle",
|
||||||
Clear: "Bağlam temizlendi",
|
|
||||||
Revert: "Bağlamı geri getir",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Eklenti",
|
Name: "Plugin",
|
||||||
},
|
},
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Sen bir asistansın",
|
Sysmessage: "Sen bir asistansın",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Maske",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Önceden Tanımlı Karakter Maskeleri",
|
Title: "Prompt Template",
|
||||||
SubTitle: (count: number) =>
|
SubTitle: (count: number) => `${count} prompt templates`,
|
||||||
`${count} tane önceden tanımlı karakter tanımı`,
|
Search: "Search Templates",
|
||||||
Search: "Karakter maskesi ara",
|
Create: "Create",
|
||||||
Create: "Yeni oluştur",
|
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `${count} tane önceden tanımlı sohbet içeriyor`,
|
Info: (count: number) => `${count} prompts`,
|
||||||
Chat: "Sohbet",
|
Chat: "Chat",
|
||||||
View: "Görüntüle",
|
View: "View",
|
||||||
Edit: "Düzenle",
|
Edit: "Edit",
|
||||||
Delete: "Sil",
|
Delete: "Delete",
|
||||||
DeleteConfirm: "Silmek istediğinizden emin misiniz?",
|
DeleteConfirm: "Confirm to delete?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Önceden Tanımlı Maskeyi Düzenle ${readonly ? " (Salt Okunur)" : ""}`,
|
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
|
||||||
Download: "Önceden Tanımlı Maskeyi İndir",
|
Download: "Download",
|
||||||
Clone: "Önceden Tanımlı Maskeyi Kopyala",
|
Clone: "Clone",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Karakter Profil Resmi",
|
Avatar: "Bot Avatar",
|
||||||
Name: "Karakter Adı",
|
Name: "Bot Name",
|
||||||
Sync: {
|
|
||||||
Title: "Küresel Ayarları Kullan",
|
|
||||||
SubTitle: "Mevcut sohbet küresel model ayarlarını mı kullanacak?",
|
|
||||||
Confirm:
|
|
||||||
"Mevcut sohbetin özelleştirilmiş ayarları otomatik olarak üzerine yazılacaktır, küresel ayarları etkinleştirmek istediğinizden emin misiniz?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Önceden Tanımlı Sohbetleri Gizle",
|
|
||||||
SubTitle:
|
|
||||||
"Gizlendiğinde, önceden tanımlı sohbetler sohbet ekranında görünmeyecek",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Bu Maskeyi Paylaş",
|
|
||||||
SubTitle: "Bu maskenin doğrudan bağlantısını oluştur",
|
|
||||||
Action: "Bağlantıyı Kopyala",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Geri dön",
|
Return: "Return",
|
||||||
Skip: "Doğrudan başla",
|
Skip: "Skip",
|
||||||
NotShow: "Bir daha gösterme",
|
Title: "Pick a Mask",
|
||||||
ConfirmNoShow:
|
SubTitle: "Chat with the Soul behind the Mask",
|
||||||
"Devre dışı bırakmak istediğinizden emin misiniz? Devre dışı bıraktıktan sonra ayarlardan tekrar etkinleştirebilirsiniz.",
|
More: "Find More",
|
||||||
Title: "Bir Maske Seçin",
|
NotShow: "Not Show Again",
|
||||||
SubTitle:
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
"Şimdi başlayın ve maskenin arkasındaki zihinle etkileşimde bulunun",
|
|
||||||
More: "Tümünü Gör",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Bağlantıda erişim kodu bulundu, otomatik olarak doldurulsun mu?",
|
|
||||||
Settings:
|
|
||||||
"Bağlantıda önceden tanımlı ayarlar bulundu, otomatik olarak doldurulsun mu?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Onayla",
|
Confirm: "Confirm",
|
||||||
Cancel: "İptal et",
|
Cancel: "Cancel",
|
||||||
Close: "Kapat",
|
Close: "Close",
|
||||||
Create: "Yeni oluştur",
|
Create: "Create",
|
||||||
Edit: "Düzenle",
|
Edit: "Edit",
|
||||||
Export: "Dışa Aktar",
|
|
||||||
Import: "İçe Aktar",
|
|
||||||
Sync: "Senkronize et",
|
|
||||||
Config: "Yapılandır",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Sadece bağlam temizlendikten sonraki mesajlar gösterilecektir",
|
|
||||||
},
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
Messages: "Mesajlar",
|
Messages: "Mesajlar",
|
||||||
Topic: "Konu",
|
Topic: "Konu",
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import { SubmitKey } from "../store/config";
|
|||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
const tw = {
|
const tw = {
|
||||||
WIP: "此功能仍在開發中……",
|
WIP: "該功能仍在開發中……",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized: isApp
|
||||||
? "偵測到無效的 API Key,請前往[設定](/#/settings)頁面檢查 API Key 是否設定正確。"
|
? "檢測到無效 API Key,請前往[設定](/#/settings)頁檢查 API Key 是否設定正確。"
|
||||||
: "存取密碼不正確或尚未填寫,請前往[登入](/#/auth)頁面輸入正確的存取密碼,或者在[設定](/#/settings)頁面填入你自己的 OpenAI API Key。",
|
: "訪問密碼不正確或為空,請前往[登入](/#/auth)頁輸入正確的訪問密碼,或者在[設定](/#/settings)頁填入你自己的 OpenAI API Key。",
|
||||||
},
|
},
|
||||||
|
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "需要密碼",
|
Title: "需要密碼",
|
||||||
Tips: "管理員開啟了密碼驗證,請在下方填入存取密碼",
|
Tips: "管理員開啟了密碼驗證,請在下方填入訪問碼",
|
||||||
SubTips: "或者輸入你的 OpenAI 或 Google API 金鑰",
|
SubTips: "或者輸入你的 OpenAI 或 Google API 密鑰",
|
||||||
Input: "在此處填寫存取密碼",
|
Input: "在此處填寫訪問碼",
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
Later: "稍候再說",
|
Later: "稍候再說",
|
||||||
},
|
},
|
||||||
@@ -25,10 +25,10 @@ const tw = {
|
|||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 則對話`,
|
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 則對話`,
|
||||||
EditMessage: {
|
EditMessage: {
|
||||||
Title: "編輯訊息記錄",
|
Title: "編輯消息記錄",
|
||||||
Topic: {
|
Topic: {
|
||||||
Title: "聊天主題",
|
Title: "聊天主題",
|
||||||
SubTitle: "更改目前聊天主題",
|
SubTitle: "更改當前聊天主題",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: {
|
Actions: {
|
||||||
@@ -40,13 +40,13 @@ const tw = {
|
|||||||
Retry: "重試",
|
Retry: "重試",
|
||||||
Pin: "固定",
|
Pin: "固定",
|
||||||
PinToastContent: "已將 1 條對話固定至預設提示詞",
|
PinToastContent: "已將 1 條對話固定至預設提示詞",
|
||||||
PinToastAction: "檢視",
|
PinToastAction: "查看",
|
||||||
Delete: "刪除",
|
Delete: "刪除",
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
},
|
},
|
||||||
Commands: {
|
Commands: {
|
||||||
new: "新建聊天",
|
new: "新建聊天",
|
||||||
newm: "從角色範本新建聊天",
|
newm: "從面具新建聊天",
|
||||||
next: "下一個聊天",
|
next: "下一個聊天",
|
||||||
prev: "上一個聊天",
|
prev: "上一個聊天",
|
||||||
clear: "清除上下文",
|
clear: "清除上下文",
|
||||||
@@ -61,7 +61,7 @@ const tw = {
|
|||||||
dark: "深色模式",
|
dark: "深色模式",
|
||||||
},
|
},
|
||||||
Prompt: "快捷指令",
|
Prompt: "快捷指令",
|
||||||
Masks: "所有角色範本",
|
Masks: "所有面具",
|
||||||
Clear: "清除聊天",
|
Clear: "清除聊天",
|
||||||
Settings: "對話設定",
|
Settings: "對話設定",
|
||||||
UploadImage: "上傳圖片",
|
UploadImage: "上傳圖片",
|
||||||
@@ -90,27 +90,27 @@ const tw = {
|
|||||||
MessageFromYou: "來自您的訊息",
|
MessageFromYou: "來自您的訊息",
|
||||||
MessageFromChatGPT: "來自 ChatGPT 的訊息",
|
MessageFromChatGPT: "來自 ChatGPT 的訊息",
|
||||||
Format: {
|
Format: {
|
||||||
Title: "匯出格式",
|
Title: "導出格式",
|
||||||
SubTitle: "可以匯出 Markdown 文字檔或者 PNG 圖片",
|
SubTitle: "可以導出 Markdown 文本或者 PNG 圖片",
|
||||||
},
|
},
|
||||||
IncludeContext: {
|
IncludeContext: {
|
||||||
Title: "包含角色範本上下文",
|
Title: "包含面具上下文",
|
||||||
SubTitle: "是否在訊息中顯示角色範本上下文",
|
SubTitle: "是否在消息中展示面具上下文",
|
||||||
},
|
},
|
||||||
Steps: {
|
Steps: {
|
||||||
Select: "選取",
|
Select: "選取",
|
||||||
Preview: "預覽",
|
Preview: "預覽",
|
||||||
},
|
},
|
||||||
Image: {
|
Image: {
|
||||||
Toast: "正在產生截圖",
|
Toast: "正在生成截圖",
|
||||||
Modal: "長按或按右鍵儲存圖片",
|
Modal: "長按或右鍵保存圖片",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Select: {
|
Select: {
|
||||||
Search: "查詢訊息",
|
Search: "查詢消息",
|
||||||
All: "選取全部",
|
All: "選取全部",
|
||||||
Latest: "最近幾條",
|
Latest: "最近幾條",
|
||||||
Clear: "清除選取",
|
Clear: "清除選中",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "上下文記憶 Prompt",
|
Title: "上下文記憶 Prompt",
|
||||||
@@ -121,7 +121,7 @@ const tw = {
|
|||||||
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "開新對話",
|
NewChat: "新的對話",
|
||||||
DeleteChat: "確定要刪除選取的對話嗎?",
|
DeleteChat: "確定要刪除選取的對話嗎?",
|
||||||
DeleteToast: "已刪除對話",
|
DeleteToast: "已刪除對話",
|
||||||
Revert: "撤銷",
|
Revert: "撤銷",
|
||||||
@@ -132,10 +132,10 @@ const tw = {
|
|||||||
|
|
||||||
Danger: {
|
Danger: {
|
||||||
Reset: {
|
Reset: {
|
||||||
Title: "重設所有設定",
|
Title: "重置所有設定",
|
||||||
SubTitle: "重設所有設定項回預設值",
|
SubTitle: "重置所有設定項回預設值",
|
||||||
Action: "立即重設",
|
Action: "立即重置",
|
||||||
Confirm: "確認重設所有設定?",
|
Confirm: "確認重置所有設定?",
|
||||||
},
|
},
|
||||||
Clear: {
|
Clear: {
|
||||||
Title: "清除所有資料",
|
Title: "清除所有資料",
|
||||||
@@ -153,18 +153,13 @@ const tw = {
|
|||||||
Title: "字型大小",
|
Title: "字型大小",
|
||||||
SubTitle: "聊天內容的字型大小",
|
SubTitle: "聊天內容的字型大小",
|
||||||
},
|
},
|
||||||
FontFamily: {
|
|
||||||
Title: "聊天字體",
|
|
||||||
SubTitle: "聊天內容的字體,若置空則應用全局默認字體",
|
|
||||||
Placeholder: "字體名稱",
|
|
||||||
},
|
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "匯入系統提示",
|
Title: "匯入系統提示",
|
||||||
SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示",
|
SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
InputTemplate: {
|
||||||
Title: "使用者輸入預處理",
|
Title: "用戶輸入預處理",
|
||||||
SubTitle: "使用者最新的一則訊息會填充到此範本",
|
SubTitle: "用戶最新的一條消息會填充到此模板",
|
||||||
},
|
},
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
@@ -183,8 +178,8 @@ const tw = {
|
|||||||
SubTitle: "在預覽氣泡中預覽 Markdown 內容",
|
SubTitle: "在預覽氣泡中預覽 Markdown 內容",
|
||||||
},
|
},
|
||||||
AutoGenerateTitle: {
|
AutoGenerateTitle: {
|
||||||
Title: "自動產生標題",
|
Title: "自動生成標題",
|
||||||
SubTitle: "根據對話內容產生合適的標題",
|
SubTitle: "根據對話內容生成合適的標題",
|
||||||
},
|
},
|
||||||
Sync: {
|
Sync: {
|
||||||
CloudState: "雲端資料",
|
CloudState: "雲端資料",
|
||||||
@@ -199,20 +194,20 @@ const tw = {
|
|||||||
},
|
},
|
||||||
SyncType: {
|
SyncType: {
|
||||||
Title: "同步類型",
|
Title: "同步類型",
|
||||||
SubTitle: "選擇偏好的同步伺服器",
|
SubTitle: "選擇喜愛的同步服務器",
|
||||||
},
|
},
|
||||||
Proxy: {
|
Proxy: {
|
||||||
Title: "啟用代理伺服器",
|
Title: "啟用代理",
|
||||||
SubTitle: "在瀏覽器中同步時,啟用代理伺服器以避免跨域限制",
|
SubTitle: "在瀏覽器中同步時,必須啟用代理以避免跨域限制",
|
||||||
},
|
},
|
||||||
ProxyUrl: {
|
ProxyUrl: {
|
||||||
Title: "代理伺服器位置",
|
Title: "代理地址",
|
||||||
SubTitle: "僅適用於本專案內建的跨域代理",
|
SubTitle: "僅適用於本項目自帶的跨域代理",
|
||||||
},
|
},
|
||||||
|
|
||||||
WebDav: {
|
WebDav: {
|
||||||
Endpoint: "WebDAV 位置",
|
Endpoint: "WebDAV 地址",
|
||||||
UserName: "使用者名稱",
|
UserName: "用戶名",
|
||||||
Password: "密碼",
|
Password: "密碼",
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -223,20 +218,20 @@ const tw = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
LocalState: "本機資料",
|
LocalState: "本地資料",
|
||||||
Overview: (overview: any) => {
|
Overview: (overview: any) => {
|
||||||
return `${overview.chat} 次對話,${overview.message} 則訊息,${overview.prompt} 條提示詞,${overview.mask} 個角色範本`;
|
return `${overview.chat} 次對話,${overview.message} 條消息,${overview.prompt} 條提示詞,${overview.mask} 個面具`;
|
||||||
},
|
},
|
||||||
ImportFailed: "匯入失敗",
|
ImportFailed: "導入失敗",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "角色範本啟動頁面",
|
Title: "面具啟動頁面",
|
||||||
SubTitle: "新增聊天時,呈現角色範本啟動頁面",
|
SubTitle: "新增聊天時,呈現面具啟動頁面",
|
||||||
},
|
},
|
||||||
Builtin: {
|
Builtin: {
|
||||||
Title: "隱藏內建角色範本",
|
Title: "隱藏內置面具",
|
||||||
SubTitle: "在所有角色範本列表中隱藏內建角色範本",
|
SubTitle: "在所有面具列表中隱藏內置面具",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
@@ -244,13 +239,13 @@ const tw = {
|
|||||||
Title: "停用提示詞自動補齊",
|
Title: "停用提示詞自動補齊",
|
||||||
SubTitle: "在輸入框開頭輸入 / 即可觸發自動補齊",
|
SubTitle: "在輸入框開頭輸入 / 即可觸發自動補齊",
|
||||||
},
|
},
|
||||||
List: "自訂提示詞列表",
|
List: "自定義提示詞列表",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`內建 ${builtin} 條,使用者自訂 ${custom} 條`,
|
`內建 ${builtin} 條,使用者定義 ${custom} 條`,
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "提示詞列表",
|
Title: "提示詞列表",
|
||||||
Add: "新增一則",
|
Add: "新增一條",
|
||||||
Search: "搜尋提示詞",
|
Search: "搜尋提示詞",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
@@ -278,74 +273,74 @@ const tw = {
|
|||||||
|
|
||||||
Access: {
|
Access: {
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "存取密碼",
|
Title: "訪問密碼",
|
||||||
SubTitle: "管理員已開啟加密存取",
|
SubTitle: "管理員已開啟加密訪問",
|
||||||
Placeholder: "請輸入存取密碼",
|
Placeholder: "請輸入訪問密碼",
|
||||||
},
|
},
|
||||||
CustomEndpoint: {
|
CustomEndpoint: {
|
||||||
Title: "自訂 API 端點 (Endpoint)",
|
Title: "自定義接口 (Endpoint)",
|
||||||
SubTitle: "是否使用自訂 Azure 或 OpenAI 服務",
|
SubTitle: "是否使用自定義 Azure 或 OpenAI 服務",
|
||||||
},
|
},
|
||||||
Provider: {
|
Provider: {
|
||||||
Title: "模型供應商",
|
Title: "模型服務商",
|
||||||
SubTitle: "切換不同的服務供應商",
|
SubTitle: "切換不同的服務商",
|
||||||
},
|
},
|
||||||
OpenAI: {
|
OpenAI: {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API Key",
|
Title: "API Key",
|
||||||
SubTitle: "使用自訂 OpenAI Key 繞過密碼存取限制",
|
SubTitle: "使用自定義 OpenAI Key 繞過密碼訪問限制",
|
||||||
Placeholder: "OpenAI API Key",
|
Placeholder: "OpenAI API Key",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "API 端點 (Endpoint) 位址",
|
Title: "接口(Endpoint) 地址",
|
||||||
SubTitle: "除預設位址外,必須包含 http(s)://",
|
SubTitle: "除默認地址外,必須包含 http(s)://",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Azure: {
|
Azure: {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API 金鑰",
|
Title: "接口密鑰",
|
||||||
SubTitle: "使用自訂 Azure Key 繞過密碼存取限制",
|
SubTitle: "使用自定義 Azure Key 繞過密碼訪問限制",
|
||||||
Placeholder: "Azure API Key",
|
Placeholder: "Azure API Key",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "API 端點 (Endpoint) 位址",
|
Title: "接口(Endpoint) 地址",
|
||||||
SubTitle: "範例:",
|
SubTitle: "樣例:",
|
||||||
},
|
},
|
||||||
|
|
||||||
ApiVerion: {
|
ApiVerion: {
|
||||||
Title: "API 版本 (azure api version)",
|
Title: "接口版本 (azure api version)",
|
||||||
SubTitle: "指定一個特定的 API 版本",
|
SubTitle: "選擇指定的部分版本",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Anthropic: {
|
Anthropic: {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API 金鑰",
|
Title: "API 密鑰",
|
||||||
SubTitle: "從 Anthropic AI 取得您的 API 金鑰",
|
SubTitle: "從 Anthropic AI 獲取您的 API 密鑰",
|
||||||
Placeholder: "Anthropic API Key",
|
Placeholder: "Anthropic API Key",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "端點位址",
|
Title: "終端地址",
|
||||||
SubTitle: "範例:",
|
SubTitle: "示例:",
|
||||||
},
|
},
|
||||||
|
|
||||||
ApiVerion: {
|
ApiVerion: {
|
||||||
Title: "API 版本 (claude api version)",
|
Title: "API 版本 (claude api version)",
|
||||||
SubTitle: "指定一個特定的 API 版本",
|
SubTitle: "選擇一個特定的 API 版本输入",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Google: {
|
Google: {
|
||||||
ApiKey: {
|
ApiKey: {
|
||||||
Title: "API 金鑰",
|
Title: "API 密鑰",
|
||||||
SubTitle: "從 Google AI 取得您的 API 金鑰",
|
SubTitle: "從 Google AI 獲取您的 API 密鑰",
|
||||||
Placeholder: "輸入您的 Google AI Studio API 金鑰",
|
Placeholder: "輸入您的 Google AI Studio API 密鑰",
|
||||||
},
|
},
|
||||||
|
|
||||||
Endpoint: {
|
Endpoint: {
|
||||||
Title: "端點位址",
|
Title: "終端地址",
|
||||||
SubTitle: "範例:",
|
SubTitle: "示例:",
|
||||||
},
|
},
|
||||||
|
|
||||||
ApiVersion: {
|
ApiVersion: {
|
||||||
@@ -354,8 +349,8 @@ const tw = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
CustomModel: {
|
CustomModel: {
|
||||||
Title: "自訂模型名稱",
|
Title: "自定義模型名",
|
||||||
SubTitle: "增加自訂模型可選擇項目,使用英文逗號隔開",
|
SubTitle: "增加自定義模型可選項,使用英文逗號隔開",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -365,7 +360,7 @@ const tw = {
|
|||||||
SubTitle: "值越大,回應越隨機",
|
SubTitle: "值越大,回應越隨機",
|
||||||
},
|
},
|
||||||
TopP: {
|
TopP: {
|
||||||
Title: "核心採樣 (top_p)",
|
Title: "核采樣 (top_p)",
|
||||||
SubTitle: "與隨機性類似,但不要和隨機性一起更改",
|
SubTitle: "與隨機性類似,但不要和隨機性一起更改",
|
||||||
},
|
},
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
@@ -405,18 +400,18 @@ const tw = {
|
|||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `已設定 ${x} 條前置上下文`,
|
Toast: (x: any) => `已設定 ${x} 條前置上下文`,
|
||||||
Edit: "前置上下文和歷史記憶",
|
Edit: "前置上下文和歷史記憶",
|
||||||
Add: "新增一則",
|
Add: "新增一條",
|
||||||
Clear: "上下文已清除",
|
Clear: "上下文已清除",
|
||||||
Revert: "恢復上下文",
|
Revert: "恢復上下文",
|
||||||
},
|
},
|
||||||
Plugin: { Name: "外掛" },
|
Plugin: { Name: "外掛" },
|
||||||
FineTuned: { Sysmessage: "你是一個助手" },
|
FineTuned: { Sysmessage: "你是一個助手" },
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "角色範本",
|
Name: "面具",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "預設角色角色範本",
|
Title: "預設角色面具",
|
||||||
SubTitle: (count: number) => `${count} 個預設角色定義`,
|
SubTitle: (count: number) => `${count} 個預設角色定義`,
|
||||||
Search: "搜尋角色角色範本",
|
Search: "搜尋角色面具",
|
||||||
Create: "新增",
|
Create: "新增",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
@@ -429,41 +424,41 @@ const tw = {
|
|||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`編輯預設角色範本 ${readonly ? "(唯讀)" : ""}`,
|
`編輯預設面具 ${readonly ? "(只讀)" : ""}`,
|
||||||
Download: "下載預設值",
|
Download: "下載預設",
|
||||||
Clone: "以此預設值建立副本",
|
Clone: "複製預設",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "角色頭像",
|
Avatar: "角色頭像",
|
||||||
Name: "角色名稱",
|
Name: "角色名稱",
|
||||||
Sync: {
|
Sync: {
|
||||||
Title: "使用全域設定",
|
Title: "使用全局設定",
|
||||||
SubTitle: "目前對話是否使用全域模型設定",
|
SubTitle: "當前對話是否使用全局模型設定",
|
||||||
Confirm: "目前對話的自訂設定將會被自動覆蓋,確認啟用全域設定?",
|
Confirm: "當前對話的自定義設定將會被自動覆蓋,確認啟用全局設定?",
|
||||||
},
|
},
|
||||||
HideContext: {
|
HideContext: {
|
||||||
Title: "隱藏預設對話",
|
Title: "隱藏預設對話",
|
||||||
SubTitle: "隱藏後預設對話不會出現在聊天介面",
|
SubTitle: "隱藏後預設對話不會出現在聊天界面",
|
||||||
},
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "分享此角色範本",
|
Title: "分享此面具",
|
||||||
SubTitle: "產生此角色範本的直達連結",
|
SubTitle: "生成此面具的直達鏈接",
|
||||||
Action: "複製連結",
|
Action: "覆制鏈接",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "返回",
|
Return: "返回",
|
||||||
Skip: "跳過",
|
Skip: "跳過",
|
||||||
NotShow: "不再顯示",
|
NotShow: "不再呈現",
|
||||||
ConfirmNoShow: "確認停用?停用後可以隨時在設定中重新啟用。",
|
ConfirmNoShow: "確認停用?停用後可以隨時在設定中重新啟用。",
|
||||||
Title: "挑選一個角色範本",
|
Title: "挑選一個面具",
|
||||||
SubTitle: "現在開始,與角色範本背後的靈魂思維碰撞",
|
SubTitle: "現在開始,與面具背後的靈魂思維碰撞",
|
||||||
More: "搜尋更多",
|
More: "搜尋更多",
|
||||||
},
|
},
|
||||||
URLCommand: {
|
URLCommand: {
|
||||||
Code: "偵測到連結中已經包含存取密碼,是否自動填入?",
|
Code: "檢測到連結中已經包含訪問碼,是否自動填入?",
|
||||||
Settings: "偵測到連結中包含了預設設定,是否自動填入?",
|
Settings: "檢測到連結中包含了預設設定,是否自動填入?",
|
||||||
},
|
},
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
@@ -471,14 +466,14 @@ const tw = {
|
|||||||
Close: "關閉",
|
Close: "關閉",
|
||||||
Create: "新增",
|
Create: "新增",
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Export: "匯出",
|
Export: "導出",
|
||||||
Import: "匯入",
|
Import: "導入",
|
||||||
Sync: "同步",
|
Sync: "同步",
|
||||||
Config: "設定",
|
Config: "設定",
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
Description: {
|
||||||
Title: "只有清除上下文之後的訊息會被顯示",
|
Title: "只有清除上下文之後的消息會被展示",
|
||||||
},
|
},
|
||||||
Model: "模型",
|
Model: "模型",
|
||||||
Messages: "訊息",
|
Messages: "訊息",
|
||||||
|
|||||||
@@ -1,464 +1,179 @@
|
|||||||
import { SubmitKey } from "../store/config";
|
import { SubmitKey } from "../store/config";
|
||||||
import type { PartialLocaleType } from "./index";
|
import type { PartialLocaleType } from "./index";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
|
||||||
|
|
||||||
const vi: PartialLocaleType = {
|
const vi: PartialLocaleType = {
|
||||||
WIP: "Sắp ra mắt...",
|
WIP: "Sắp ra mắt...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized: isApp
|
Unauthorized:
|
||||||
? "Phát hiện khóa API không hợp lệ, vui lòng truy cập trang [Cài đặt](/#/settings) để kiểm tra xem khóa API có được cấu hình chính xác không."
|
"Truy cập chưa xác thực, vui lòng nhập mã truy cập trong trang cài đặt.",
|
||||||
: "Mật khẩu truy cập không đúng hoặc để trống, vui lòng truy cập trang [Đăng nhập](/#/auth) để nhập mật khẩu truy cập chính xác, hoặc điền khóa API OpenAI của bạn vào trang [Cài đặt](/#/settings).",
|
|
||||||
},
|
|
||||||
Auth: {
|
|
||||||
Title: "Cần mật khẩu",
|
|
||||||
Tips: "Quản trị viên đã bật xác thực mật khẩu, vui lòng nhập mã truy cập ở dưới",
|
|
||||||
SubTips: "Hoặc nhập khóa API OpenAI hoặc Google của bạn",
|
|
||||||
Input: "Nhập mã truy cập tại đây",
|
|
||||||
Confirm: "Xác nhận",
|
|
||||||
Later: "Để sau",
|
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} cuộc trò chuyện`,
|
ChatItemCount: (count: number) => `${count} tin nhắn`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `Tổng cộng ${count} cuộc trò chuyện`,
|
SubTitle: (count: number) => `${count} tin nhắn với ChatGPT`,
|
||||||
EditMessage: {
|
|
||||||
Title: "Chỉnh sửa ghi chép tin nhắn",
|
|
||||||
Topic: {
|
|
||||||
Title: "Chủ đề trò chuyện",
|
|
||||||
SubTitle: "Thay đổi chủ đề trò chuyện hiện tại",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "Xem danh sách tin nhắn",
|
ChatList: "Xem danh sách chat",
|
||||||
CompressedHistory: "Xem lịch sử Prompt đã nén",
|
CompressedHistory: "Nén tin nhắn trong quá khứ",
|
||||||
Export: "Xuất khẩu ghi chép trò chuyện",
|
Export: "Xuất tất cả tin nhắn dưới dạng Markdown",
|
||||||
Copy: "Sao chép",
|
Copy: "Sao chép",
|
||||||
Stop: "Dừng lại",
|
Stop: "Dừng",
|
||||||
Retry: "Thử lại",
|
Retry: "Thử lại",
|
||||||
Pin: "Ghim",
|
|
||||||
PinToastContent: "Đã ghim 1 cuộc trò chuyện vào lời nhắc đã đặt sẵn",
|
|
||||||
PinToastAction: "Xem",
|
|
||||||
Delete: "Xóa",
|
Delete: "Xóa",
|
||||||
Edit: "Chỉnh sửa",
|
|
||||||
},
|
},
|
||||||
Commands: {
|
Rename: "Đổi tên",
|
||||||
new: "Tạo cuộc trò chuyện mới",
|
|
||||||
newm: "Tạo cuộc trò chuyện từ mặt nạ",
|
|
||||||
next: "Cuộc trò chuyện tiếp theo",
|
|
||||||
prev: "Cuộc trò chuyện trước đó",
|
|
||||||
clear: "Xóa ngữ cảnh",
|
|
||||||
del: "Xóa cuộc trò chuyện",
|
|
||||||
},
|
|
||||||
InputActions: {
|
|
||||||
Stop: "Dừng phản hồi",
|
|
||||||
ToBottom: "Cuộn đến tin nhắn mới nhất",
|
|
||||||
Theme: {
|
|
||||||
auto: "Chủ đề tự động",
|
|
||||||
light: "Chế độ sáng",
|
|
||||||
dark: "Chế độ tối",
|
|
||||||
},
|
|
||||||
Prompt: "Lệnh tắt",
|
|
||||||
Masks: "Tất cả mặt nạ",
|
|
||||||
Clear: "Xóa cuộc trò chuyện",
|
|
||||||
Settings: "Cài đặt trò chuyện",
|
|
||||||
UploadImage: "Tải lên hình ảnh",
|
|
||||||
},
|
|
||||||
Rename: "Đổi tên cuộc trò chuyện",
|
|
||||||
Typing: "Đang nhập…",
|
Typing: "Đang nhập…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `${submitKey} gửi`;
|
var inputHints = `${submitKey} để gửi`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter xuống dòng";
|
inputHints += ", Shift + Enter để xuống dòng";
|
||||||
}
|
}
|
||||||
return inputHints + ",/ kích hoạt hoàn chỉnh, : kích hoạt lệnh";
|
return inputHints + ", / để tìm kiếm mẫu gợi ý";
|
||||||
},
|
},
|
||||||
Send: "Gửi",
|
Send: "Gửi",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "Xóa trí nhớ",
|
Reset: "Khôi phục cài đặt gốc",
|
||||||
SaveAs: "Lưu dưới dạng mặt nạ",
|
SaveAs: "Lưu dưới dạng Mẫu",
|
||||||
},
|
},
|
||||||
IsContext: "Lời nhắc đã đặt sẵn",
|
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Chia sẻ ghi chép trò chuyện",
|
Title: "Tất cả tin nhắn",
|
||||||
Copy: "Sao chép tất cả",
|
Copy: "Sao chép tất cả",
|
||||||
Download: "Tải xuống tệp",
|
Download: "Tải xuống",
|
||||||
Share: "Chia sẻ lên ShareGPT",
|
MessageFromYou: "Tin nhắn của bạn",
|
||||||
MessageFromYou: "Người dùng",
|
MessageFromChatGPT: "Tin nhắn từ ChatGPT",
|
||||||
MessageFromChatGPT: "ChatGPT",
|
|
||||||
Format: {
|
|
||||||
Title: "Định dạng xuất khẩu",
|
|
||||||
SubTitle: "Có thể xuất khẩu dưới dạng văn bản Markdown hoặc hình ảnh PNG",
|
|
||||||
},
|
|
||||||
IncludeContext: {
|
|
||||||
Title: "Bao gồm ngữ cảnh mặt nạ",
|
|
||||||
SubTitle: "Có hiển thị ngữ cảnh mặt nạ trong tin nhắn không",
|
|
||||||
},
|
|
||||||
Steps: {
|
|
||||||
Select: "Chọn",
|
|
||||||
Preview: "Xem trước",
|
|
||||||
},
|
|
||||||
Image: {
|
|
||||||
Toast: "Đang tạo ảnh chụp màn hình",
|
|
||||||
Modal: "Nhấn giữ hoặc nhấp chuột phải để lưu hình ảnh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Select: {
|
|
||||||
Search: "Tìm kiếm tin nhắn",
|
|
||||||
All: "Chọn tất cả",
|
|
||||||
Latest: "Một vài tin nhắn gần đây",
|
|
||||||
Clear: "Xóa lựa chọn",
|
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
Title: "Tóm tắt lịch sử",
|
Title: "Lịch sử tin nhắn",
|
||||||
EmptyContent: "Nội dung trò chuyện quá ngắn, không cần tóm tắt",
|
EmptyContent: "Chưa có tin nhắn",
|
||||||
Send: "Tự động nén ghi chép trò chuyện và gửi dưới dạng ngữ cảnh",
|
Send: "Gửi tin nhắn trong quá khứ",
|
||||||
Copy: "Sao chép tóm tắt",
|
Copy: "Sao chép tin nhắn trong quá khứ",
|
||||||
Reset: "[unused]",
|
Reset: "Đặt lại phiên",
|
||||||
ResetConfirm: "Xác nhận xóa tóm tắt lịch sử?",
|
ResetConfirm:
|
||||||
|
"Đặt lại sẽ xóa toàn bộ lịch sử trò chuyện hiện tại và bộ nhớ. Bạn có chắc chắn muốn đặt lại không?",
|
||||||
},
|
},
|
||||||
Home: {
|
Home: {
|
||||||
NewChat: "Cuộc trò chuyện mới",
|
NewChat: "Cuộc trò chuyện mới",
|
||||||
DeleteChat: "Xác nhận xóa cuộc trò chuyện đã chọn?",
|
DeleteChat: "Xác nhận xóa các cuộc trò chuyện đã chọn?",
|
||||||
DeleteToast: "Đã xóa cuộc trò chuyện",
|
DeleteToast: "Đã xóa cuộc trò chuyện",
|
||||||
Revert: "Hoàn tác",
|
Revert: "Khôi phục",
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
Title: "Cài đặt",
|
Title: "Cài đặt",
|
||||||
SubTitle: "Tất cả các tùy chọn cài đặt",
|
SubTitle: "Tất cả cài đặt",
|
||||||
|
|
||||||
Danger: {
|
|
||||||
Reset: {
|
|
||||||
Title: "Đặt lại tất cả cài đặt",
|
|
||||||
SubTitle: "Đặt lại tất cả các mục cài đặt về giá trị mặc định",
|
|
||||||
Action: "Đặt lại ngay",
|
|
||||||
Confirm: "Xác nhận đặt lại tất cả cài đặt?",
|
|
||||||
},
|
|
||||||
Clear: {
|
|
||||||
Title: "Xóa tất cả dữ liệu",
|
|
||||||
SubTitle: "Xóa tất cả các cuộc trò chuyện và dữ liệu cài đặt",
|
|
||||||
Action: "Xóa ngay",
|
|
||||||
Confirm: "Xác nhận xóa tất cả cuộc trò chuyện và dữ liệu cài đặt?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Lang: {
|
Lang: {
|
||||||
Name: "Language", // CHÚ Ý: nếu bạn muốn thêm một bản dịch mới, vui lòng không dịch giá trị này, để nó là `Language`
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
All: "Tất cả ngôn ngữ",
|
All: "Tất cả ngôn ngữ",
|
||||||
},
|
},
|
||||||
Avatar: "Hình đại diện",
|
Avatar: "Ảnh đại diện",
|
||||||
FontSize: {
|
FontSize: {
|
||||||
Title: "Kích thước chữ",
|
Title: "Font chữ",
|
||||||
SubTitle: "Kích thước chữ của nội dung trò chuyện",
|
SubTitle: "Thay đổi font chữ của nội dung trò chuyện",
|
||||||
},
|
|
||||||
FontFamily: {
|
|
||||||
Title: "Phông Chữ Trò Chuyện",
|
|
||||||
SubTitle:
|
|
||||||
"Phông chữ của nội dung trò chuyện, để trống để áp dụng phông chữ mặc định toàn cầu",
|
|
||||||
Placeholder: "Tên Phông Chữ",
|
|
||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "Tiêm thông báo hệ thống",
|
Title: "Tiêm Prompt Hệ thống",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
"Buộc thêm một thông báo hệ thống giả ChatGPT vào đầu danh sách tin nhắn mỗi lần yêu cầu",
|
"Bắt buộc thêm một prompt hệ thống giả lập ChatGPT ở đầu danh sách tin nhắn cho mỗi yêu cầu",
|
||||||
},
|
},
|
||||||
InputTemplate: {
|
|
||||||
Title: "Xử lý đầu vào của người dùng",
|
|
||||||
SubTitle: "Tin nhắn mới nhất của người dùng sẽ được điền vào mẫu này",
|
|
||||||
},
|
|
||||||
|
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `Phiên bản hiện tại: ${x}`,
|
Version: (x: string) => `Phiên bản: ${x}`,
|
||||||
IsLatest: "Đã là phiên bản mới nhất",
|
IsLatest: "Phiên bản mới nhất",
|
||||||
CheckUpdate: "Kiểm tra cập nhật",
|
CheckUpdate: "Kiểm tra bản cập nhật",
|
||||||
IsChecking: "Đang kiểm tra cập nhật...",
|
IsChecking: "Kiểm tra bản cập nhật...",
|
||||||
FoundUpdate: (x: string) => `Tìm thấy phiên bản mới: ${x}`,
|
FoundUpdate: (x: string) => `Phát hiện phiên bản mới: ${x}`,
|
||||||
GoToUpdate: "Đi đến cập nhật",
|
GoToUpdate: "Cập nhật",
|
||||||
},
|
},
|
||||||
SendKey: "Phím gửi",
|
SendKey: "Phím gửi",
|
||||||
Theme: "Giao diện",
|
Theme: "Theme",
|
||||||
TightBorder: "Chế độ không viền",
|
TightBorder: "Chế độ không viền",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "Bong bóng xem trước",
|
Title: "Gửi bong bóng xem trước",
|
||||||
SubTitle: "Xem nội dung Markdown trong bong bóng xem trước",
|
SubTitle: "Xem trước nội dung markdown bằng bong bóng",
|
||||||
},
|
|
||||||
AutoGenerateTitle: {
|
|
||||||
Title: "Tự động tạo tiêu đề",
|
|
||||||
SubTitle: "Tạo tiêu đề phù hợp dựa trên nội dung cuộc trò chuyện",
|
|
||||||
},
|
|
||||||
Sync: {
|
|
||||||
CloudState: "Dữ liệu đám mây",
|
|
||||||
NotSyncYet: "Chưa thực hiện đồng bộ",
|
|
||||||
Success: "Đồng bộ thành công",
|
|
||||||
Fail: "Đồng bộ thất bại",
|
|
||||||
|
|
||||||
Config: {
|
|
||||||
Modal: {
|
|
||||||
Title: "Cấu hình đồng bộ đám mây",
|
|
||||||
Check: "Kiểm tra khả dụng",
|
|
||||||
},
|
|
||||||
SyncType: {
|
|
||||||
Title: "Loại đồng bộ",
|
|
||||||
SubTitle: "Chọn máy chủ đồng bộ ưa thích",
|
|
||||||
},
|
|
||||||
Proxy: {
|
|
||||||
Title: "Kích hoạt proxy",
|
|
||||||
SubTitle:
|
|
||||||
"Khi đồng bộ qua trình duyệt, cần kích hoạt proxy để tránh hạn chế ngang miền",
|
|
||||||
},
|
|
||||||
ProxyUrl: {
|
|
||||||
Title: "Địa chỉ proxy",
|
|
||||||
SubTitle: "Chỉ áp dụng cho proxy ngang miền của dự án này",
|
|
||||||
},
|
|
||||||
|
|
||||||
WebDav: {
|
|
||||||
Endpoint: "Địa chỉ WebDAV",
|
|
||||||
UserName: "Tên người dùng",
|
|
||||||
Password: "Mật khẩu",
|
|
||||||
},
|
|
||||||
|
|
||||||
UpStash: {
|
|
||||||
Endpoint: "URL UpStash Redis REST",
|
|
||||||
UserName: "Tên sao lưu",
|
|
||||||
Password: "Token UpStash Redis REST",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalState: "Dữ liệu cục bộ",
|
|
||||||
Overview: (overview: any) => {
|
|
||||||
return `${overview.chat} cuộc trò chuyện, ${overview.message} tin nhắn, ${overview.prompt} lệnh, ${overview.mask} mặt nạ`;
|
|
||||||
},
|
|
||||||
ImportFailed: "Nhập không thành công",
|
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
Title: "Trang khởi động mặt nạ",
|
Title: "Mask Splash Screen",
|
||||||
SubTitle: "Hiển thị trang khởi động mặt nạ khi tạo cuộc trò chuyện mới",
|
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
|
||||||
},
|
|
||||||
Builtin: {
|
|
||||||
Title: "Ẩn mặt nạ tích hợp",
|
|
||||||
SubTitle: "Ẩn mặt nạ tích hợp trong danh sách tất cả mặt nạ",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Prompt: {
|
Prompt: {
|
||||||
Disable: {
|
Disable: {
|
||||||
Title: "Vô hiệu hóa tự động hoàn thành lệnh",
|
Title: "Vô hiệu hóa chức năng tự động hoàn thành",
|
||||||
SubTitle: "Nhập / ở đầu ô nhập để kích hoạt tự động hoàn thành",
|
SubTitle: "Nhập / để kích hoạt chức năng tự động hoàn thành",
|
||||||
},
|
},
|
||||||
List: "Danh sách lệnh tùy chỉnh",
|
List: "Danh sách mẫu gợi ý",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`Tích hợp ${builtin} mục, người dùng định nghĩa ${custom} mục`,
|
`${builtin} có sẵn, ${custom} do người dùng xác định`,
|
||||||
Edit: "Chỉnh sửa",
|
Edit: "Chỉnh sửa",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "Danh sách lệnh",
|
Title: "Danh sách mẫu gợi ý",
|
||||||
Add: "Tạo mới",
|
Add: "Thêm",
|
||||||
Search: "Tìm kiếm lệnh",
|
Search: "Tìm kiếm mẫu",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: "Chỉnh sửa lệnh",
|
Title: "Chỉnh sửa mẫu",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Số tin nhắn lịch sử kèm theo",
|
Title: "Số lượng tin nhắn đính kèm",
|
||||||
SubTitle: "Số tin nhắn lịch sử kèm theo mỗi yêu cầu",
|
SubTitle: "Số lượng tin nhắn trong quá khứ được gửi kèm theo mỗi yêu cầu",
|
||||||
},
|
},
|
||||||
CompressThreshold: {
|
CompressThreshold: {
|
||||||
Title: "Ngưỡng nén tin nhắn lịch sử",
|
Title: "Ngưỡng nén lịch sử tin nhắn",
|
||||||
SubTitle:
|
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
|
||||||
"Khi tin nhắn lịch sử chưa nén vượt quá giá trị này, sẽ thực hiện nén",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Usage: {
|
Usage: {
|
||||||
Title: "Tra cứu số dư",
|
Title: "Hạn mức tài khoản",
|
||||||
SubTitle(used: any, total: any) {
|
SubTitle(used: any, total: any) {
|
||||||
return `Đã sử dụng trong tháng: $${used}, Tổng số đăng ký: $${total}`;
|
return `Đã sử dụng $${used} trong tháng này, hạn mức $${total}`;
|
||||||
},
|
},
|
||||||
IsChecking: "Đang kiểm tra…",
|
IsChecking: "Đang kiểm tra...",
|
||||||
Check: "Kiểm tra lại",
|
Check: "Kiểm tra",
|
||||||
NoAccess: "Nhập khóa API hoặc mật khẩu truy cập để xem số dư",
|
NoAccess: "Nhập API Key để kiểm tra hạn mức",
|
||||||
},
|
},
|
||||||
|
|
||||||
Access: {
|
Model: "Mô hình",
|
||||||
AccessCode: {
|
|
||||||
Title: "Mật khẩu truy cập",
|
|
||||||
SubTitle: "Quản trị viên đã bật truy cập mã hóa",
|
|
||||||
Placeholder: "Nhập mật khẩu truy cập",
|
|
||||||
},
|
|
||||||
CustomEndpoint: {
|
|
||||||
Title: "Giao diện tùy chỉnh",
|
|
||||||
SubTitle: "Có sử dụng dịch vụ Azure hoặc OpenAI tùy chỉnh không",
|
|
||||||
},
|
|
||||||
Provider: {
|
|
||||||
Title: "Nhà cung cấp dịch vụ mô hình",
|
|
||||||
SubTitle: "Chuyển đổi giữa các nhà cung cấp khác nhau",
|
|
||||||
},
|
|
||||||
OpenAI: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle:
|
|
||||||
"Sử dụng khóa OpenAI tùy chỉnh để vượt qua hạn chế truy cập mật khẩu",
|
|
||||||
Placeholder: "Khóa API OpenAI",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Ngoài địa chỉ mặc định, phải bao gồm http(s)://",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Azure: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Khóa giao diện",
|
|
||||||
SubTitle:
|
|
||||||
"Sử dụng khóa Azure tùy chỉnh để vượt qua hạn chế truy cập mật khẩu",
|
|
||||||
Placeholder: "Khóa API Azure",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Ví dụ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Phiên bản giao diện (phiên bản API azure)",
|
|
||||||
SubTitle: "Chọn phiên bản phần cụ thể",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Anthropic: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Khóa giao diện",
|
|
||||||
SubTitle:
|
|
||||||
"Sử dụng khóa Anthropic tùy chỉnh để vượt qua hạn chế truy cập mật khẩu",
|
|
||||||
Placeholder: "Khóa API Anthropic",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Ví dụ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVerion: {
|
|
||||||
Title: "Phiên bản giao diện (phiên bản API claude)",
|
|
||||||
SubTitle: "Chọn một phiên bản API cụ thể để nhập",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Khóa API",
|
|
||||||
SubTitle: "Lấy khóa API từ Google AI",
|
|
||||||
Placeholder: "Nhập khóa API Google AI Studio của bạn",
|
|
||||||
},
|
|
||||||
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ cuối",
|
|
||||||
SubTitle: "Ví dụ:",
|
|
||||||
},
|
|
||||||
|
|
||||||
ApiVersion: {
|
|
||||||
Title: "Phiên bản API (chỉ áp dụng cho gemini-pro)",
|
|
||||||
SubTitle: "Chọn một phiên bản API cụ thể",
|
|
||||||
},
|
|
||||||
GoogleSafetySettings: {
|
|
||||||
Title: "Mức độ lọc an toàn Google",
|
|
||||||
SubTitle: "Cài đặt mức độ lọc nội dung",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Baidu: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "API Key",
|
|
||||||
SubTitle: "Sử dụng khóa Baidu API tùy chỉnh",
|
|
||||||
Placeholder: "Khóa API Baidu",
|
|
||||||
},
|
|
||||||
SecretKey: {
|
|
||||||
Title: "Secret Key",
|
|
||||||
SubTitle: "Sử dụng khóa bí mật Baidu tùy chỉnh",
|
|
||||||
Placeholder: "Khóa bí mật Baidu",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Không hỗ trợ tùy chỉnh, hãy cấu hình trong .env",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ByteDance: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Khóa giao diện",
|
|
||||||
SubTitle: "Sử dụng khóa ByteDance API tùy chỉnh",
|
|
||||||
Placeholder: "Khóa API ByteDance",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Ví dụ:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Alibaba: {
|
|
||||||
ApiKey: {
|
|
||||||
Title: "Khóa giao diện",
|
|
||||||
SubTitle: "Sử dụng khóa Alibaba Cloud API tùy chỉnh",
|
|
||||||
Placeholder: "Khóa API Alibaba Cloud",
|
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "Địa chỉ giao diện",
|
|
||||||
SubTitle: "Ví dụ:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CustomModel: {
|
|
||||||
Title: "Tên mô hình tùy chỉnh",
|
|
||||||
SubTitle:
|
|
||||||
"Thêm tùy chọn mô hình tùy chỉnh, sử dụng dấu phẩy để phân cách",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Model: "Mô hình (model)",
|
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Độ ngẫu nhiên (temperature)",
|
Title: "Tính ngẫu nhiên (temperature)",
|
||||||
SubTitle: "Giá trị càng lớn, câu trả lời càng ngẫu nhiên",
|
SubTitle: "Giá trị càng lớn, câu trả lời càng ngẫu nhiên",
|
||||||
},
|
},
|
||||||
TopP: {
|
|
||||||
Title: "Lấy mẫu hạt nhân (top_p)",
|
|
||||||
SubTitle: "Tương tự như độ ngẫu nhiên, nhưng không thay đổi cùng một lúc",
|
|
||||||
},
|
|
||||||
MaxTokens: {
|
MaxTokens: {
|
||||||
Title: "Giới hạn phản hồi (max_tokens)",
|
Title: "Giới hạn số lượng token (max_tokens)",
|
||||||
SubTitle: "Số Token tối đa cho mỗi tương tác",
|
SubTitle: "Số lượng token tối đa được sử dụng trong mỗi lần tương tác",
|
||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Độ mới của chủ đề (presence_penalty)",
|
Title: "Chủ đề mới (presence_penalty)",
|
||||||
SubTitle:
|
SubTitle: "Giá trị càng lớn tăng khả năng mở rộng sang các chủ đề mới",
|
||||||
"Giá trị càng lớn, khả năng mở rộng đến các chủ đề mới càng cao",
|
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "Hình phạt tần suất (frequency_penalty)",
|
Title: "Hình phạt tần suất",
|
||||||
SubTitle: "Giá trị càng lớn, khả năng giảm từ ngữ lặp lại càng cao",
|
SubTitle: "Giá trị lớn hơn làm giảm khả năng lặp lại cùng một dòng",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Store: {
|
Store: {
|
||||||
DefaultTopic: "Trò chuyện mới",
|
DefaultTopic: "Cuộc trò chuyện mới",
|
||||||
BotHello: "Có thể giúp gì cho bạn?",
|
BotHello: "Xin chào! Mình có thể giúp gì cho bạn?",
|
||||||
Error: "Đã xảy ra lỗi, vui lòng thử lại sau",
|
Error: "Có lỗi xảy ra, vui lòng thử lại sau.",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"Đây là tóm tắt cuộc trò chuyện lịch sử như tiền đề: " + content,
|
"Tóm tắt ngắn gọn cuộc trò chuyện giữa người dùng và AI: " + content,
|
||||||
Topic:
|
Topic:
|
||||||
'Sử dụng bốn đến năm từ để trả lại chủ đề tóm tắt của câu này, không giải thích, không dấu câu, không từ cảm thán, không văn bản thừa, không in đậm, nếu không có chủ đề, hãy trả lại "Tán gẫu"',
|
"Sử dụng 4 đến 5 từ tóm tắt cuộc trò chuyện này mà không có phần mở đầu, dấu chấm câu, dấu ngoặc kép, dấu chấm, ký hiệu hoặc văn bản bổ sung nào. Loại bỏ các dấu ngoặc kép kèm theo.",
|
||||||
Summarize:
|
Summarize:
|
||||||
"Tóm tắt nội dung cuộc trò chuyện một cách ngắn gọn, dùng làm gợi ý ngữ cảnh cho các lần sau, giữ trong vòng 200 từ",
|
"Tóm tắt cuộc trò chuyện này một cách ngắn gọn trong 200 từ hoặc ít hơn để sử dụng làm gợi ý cho ngữ cảnh tiếp theo.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Copy: {
|
Copy: {
|
||||||
Success: "Đã sao chép vào clipboard",
|
Success: "Sao chép vào bộ nhớ tạm",
|
||||||
Failed: "Sao chép thất bại, vui lòng cấp quyền clipboard",
|
Failed:
|
||||||
},
|
"Sao chép không thành công, vui lòng cấp quyền truy cập vào bộ nhớ tạm",
|
||||||
Download: {
|
|
||||||
Success: "Nội dung đã được tải xuống thư mục của bạn.",
|
|
||||||
Failed: "Tải xuống thất bại.",
|
|
||||||
},
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Bao gồm ${x} lệnh gợi ý đã định sẵn`,
|
Toast: (x: any) => `Sử dụng ${x} tin nhắn chứa ngữ cảnh`,
|
||||||
Edit: "Cài đặt cuộc trò chuyện hiện tại",
|
Edit: "Thiết lập ngữ cảnh và bộ nhớ",
|
||||||
Add: "Thêm một cuộc trò chuyện",
|
Add: "Thêm tin nhắn",
|
||||||
Clear: "Ngữ cảnh đã được xóa",
|
|
||||||
Revert: "Khôi phục ngữ cảnh",
|
|
||||||
},
|
},
|
||||||
Plugin: {
|
Plugin: {
|
||||||
Name: "Plugin",
|
Name: "Plugin",
|
||||||
@@ -467,83 +182,52 @@ const vi: PartialLocaleType = {
|
|||||||
Sysmessage: "Bạn là một trợ lý",
|
Sysmessage: "Bạn là một trợ lý",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Mặt nạ",
|
Name: "Mẫu",
|
||||||
Page: {
|
Page: {
|
||||||
Title: "Mặt nạ vai trò đã định sẵn",
|
Title: "Mẫu trò chuyện",
|
||||||
SubTitle: (count: number) => `${count} định nghĩa vai trò đã định sẵn`,
|
SubTitle: (count: number) => `${count} mẫu`,
|
||||||
Search: "Tìm kiếm mặt nạ vai trò",
|
Search: "Tìm kiếm mẫu",
|
||||||
Create: "Tạo mới",
|
Create: "Tạo",
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `Bao gồm ${count} cuộc trò chuyện đã định sẵn`,
|
Info: (count: number) => `${count} tin nhắn`,
|
||||||
Chat: "Trò chuyện",
|
Chat: "Chat",
|
||||||
View: "Xem",
|
View: "Xem trước",
|
||||||
Edit: "Chỉnh sửa",
|
Edit: "Chỉnh sửa",
|
||||||
Delete: "Xóa",
|
Delete: "Xóa",
|
||||||
DeleteConfirm: "Xác nhận xóa?",
|
DeleteConfirm: "Xác nhận xóa?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`Chỉnh sửa mặt nạ định sẵn ${readonly ? "(chỉ đọc)" : ""}`,
|
`Chỉnh sửa mẫu ${readonly ? "(chỉ xem)" : ""}`,
|
||||||
Download: "Tải xuống mặt nạ",
|
Download: "Tải xuống",
|
||||||
Clone: "Nhân bản mặt nạ",
|
Clone: "Tạo bản sao",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "Hình đại diện vai trò",
|
Avatar: "Ảnh đại diện bot",
|
||||||
Name: "Tên vai trò",
|
Name: "Tên bot",
|
||||||
Sync: {
|
|
||||||
Title: "Sử dụng cài đặt toàn cục",
|
|
||||||
SubTitle:
|
|
||||||
"Cuộc trò chuyện hiện tại có sử dụng cài đặt mô hình toàn cục không",
|
|
||||||
Confirm:
|
|
||||||
"Cài đặt tùy chỉnh của cuộc trò chuyện hiện tại sẽ bị ghi đè tự động, xác nhận bật cài đặt toàn cục?",
|
|
||||||
},
|
|
||||||
HideContext: {
|
|
||||||
Title: "Ẩn cuộc trò chuyện đã định sẵn",
|
|
||||||
SubTitle:
|
|
||||||
"Sau khi ẩn, cuộc trò chuyện đã định sẵn sẽ không xuất hiện trong giao diện trò chuyện",
|
|
||||||
},
|
|
||||||
Share: {
|
|
||||||
Title: "Chia sẻ mặt nạ này",
|
|
||||||
SubTitle: "Tạo liên kết trực tiếp đến mặt nạ này",
|
|
||||||
Action: "Sao chép liên kết",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NewChat: {
|
NewChat: {
|
||||||
Return: "Trở lại",
|
Return: "Quay lại",
|
||||||
Skip: "Bắt đầu ngay",
|
Skip: "Bỏ qua",
|
||||||
NotShow: "Không hiển thị nữa",
|
Title: "Chọn 1 biểu tượng",
|
||||||
ConfirmNoShow:
|
SubTitle: "Bắt đầu trò chuyện ẩn sau lớp mặt nạ",
|
||||||
"Xác nhận vô hiệu hóa? Sau khi vô hiệu hóa, bạn có thể bật lại bất cứ lúc nào trong cài đặt.",
|
More: "Tìm thêm",
|
||||||
Title: "Chọn một mặt nạ",
|
NotShow: "Không hiển thị lại",
|
||||||
SubTitle: "Bắt đầu ngay, va chạm với suy nghĩ của linh hồn đứng sau mặt nạ",
|
ConfirmNoShow: "Xác nhận tắt? Bạn có thể bật lại trong phần cài đặt.",
|
||||||
More: "Xem tất cả",
|
|
||||||
},
|
|
||||||
|
|
||||||
URLCommand: {
|
|
||||||
Code: "Phát hiện mã truy cập trong liên kết, có tự động điền không?",
|
|
||||||
Settings:
|
|
||||||
"Phát hiện cài đặt định sẵn trong liên kết, có tự động điền không?",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "Xác nhận",
|
Confirm: "Xác nhận",
|
||||||
Cancel: "Hủy",
|
Cancel: "Hủy",
|
||||||
Close: "Đóng",
|
Close: "Đóng",
|
||||||
Create: "Tạo mới",
|
Create: "Tạo",
|
||||||
Edit: "Chỉnh sửa",
|
Edit: "Chỉnh sửa",
|
||||||
Export: "Xuất",
|
|
||||||
Import: "Nhập",
|
|
||||||
Sync: "Đồng bộ",
|
|
||||||
Config: "Cấu hình",
|
|
||||||
},
|
},
|
||||||
Exporter: {
|
Exporter: {
|
||||||
Description: {
|
|
||||||
Title: "Chỉ tin nhắn sau khi xóa ngữ cảnh mới được hiển thị",
|
|
||||||
},
|
|
||||||
Model: "Mô hình",
|
Model: "Mô hình",
|
||||||
Messages: "Tin nhắn",
|
Messages: "Thông điệp",
|
||||||
Topic: "Chủ đề",
|
Topic: "Chủ đề",
|
||||||
Time: "Thời gian",
|
Time: "Thời gian",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { CN_MASKS } from "./cn";
|
|
||||||
import { TW_MASKS } from "./tw";
|
|
||||||
import { EN_MASKS } from "./en";
|
|
||||||
|
|
||||||
import { type BuiltinMask } from "./typing";
|
|
||||||
|
|
||||||
const BUILTIN_MASKS: Record<string, BuiltinMask[]> = {
|
|
||||||
cn: CN_MASKS,
|
|
||||||
tw: TW_MASKS,
|
|
||||||
en: EN_MASKS,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
fs.writeFile(
|
|
||||||
dirname + "/../../public/masks.json",
|
|
||||||
JSON.stringify(BUILTIN_MASKS, null, 4),
|
|
||||||
function (error) {
|
|
||||||
if (error) {
|
|
||||||
console.error("[Build] failed to build masks", error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Mask } from "../store/mask";
|
import { Mask } from "../store/mask";
|
||||||
import { CN_MASKS } from "./cn";
|
import { CN_MASKS } from "./cn";
|
||||||
import { TW_MASKS } from "./tw";
|
|
||||||
import { EN_MASKS } from "./en";
|
import { EN_MASKS } from "./en";
|
||||||
|
|
||||||
import { type BuiltinMask } from "./typing";
|
import { type BuiltinMask } from "./typing";
|
||||||
@@ -22,20 +21,6 @@ export const BUILTIN_MASK_STORE = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BUILTIN_MASKS: BuiltinMask[] = [];
|
export const BUILTIN_MASKS: BuiltinMask[] = [...CN_MASKS, ...EN_MASKS].map(
|
||||||
|
(m) => BUILTIN_MASK_STORE.add(m),
|
||||||
if (typeof window != "undefined") {
|
);
|
||||||
// run in browser skip in next server
|
|
||||||
fetch("/masks.json")
|
|
||||||
.then((res) => res.json())
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("[Fetch] failed to fetch masks", error);
|
|
||||||
return { cn: [], tw: [], en: [] };
|
|
||||||
})
|
|
||||||
.then((masks) => {
|
|
||||||
const { cn = [], tw = [], en = [] } = masks;
|
|
||||||
return [...cn, ...tw, ...en].map((m) => {
|
|
||||||
BUILTIN_MASKS.push(BUILTIN_MASK_STORE.add(m));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user