Compare commits

..

351 Commits

Author SHA1 Message Date
lloydzhou
23ac2efd89 hotfix and update version 2024-09-07 22:12:42 +08:00
Lloyd Zhou
daeffb2dc6 Merge pull request #5383 from SukkaW/fix-5378
fix(#5378): default plugin ids to empty array
2024-09-07 22:09:35 +08:00
SukkaW
db58ca6c1d fix(#5378): default plugin ids to empty array 2024-09-07 21:32:18 +08:00
Lloyd Zhou
2ff292cbfa Merge pull request #5381 from reggiezhang/patch-1
Add crossOrigin="use-credentials" for site.webmanifest
2024-09-07 16:58:07 +08:00
Reggie Zhang
5a81393863 Add crossOrigin="use-credentials" for site.webmanifest
Add `crossOrigin="use-credentials"` to the `<link>` element for `site.webmanifest` when the site is behind a proxy with authentication.
2024-09-07 16:24:52 +08:00
Lloyd Zhou
116a73d398 Merge pull request #5377 from ConnectAI-E/hotfix/mermaid
hotfix Mermaid can not render. close #5374
2024-09-07 13:01:36 +08:00
lloydzhou
cf0c057164 hotfix Mermaid can not render. close #5374 2024-09-07 13:00:55 +08:00
Lloyd Zhou
fe5a4f4447 Merge pull request #5375 from Kosette/fix-version
fix: update package version
2024-09-07 11:55:23 +08:00
kosette
27828d9ca8 fix: update package version 2024-09-06 23:07:01 +08:00
Dogtiti
2bd799fac6 Merge pull request #5331 from ConnectAI-E/feature/plugin
Feature plugin (GPTs like action based on function call)
2024-09-06 20:06:50 +08:00
lloydzhou
9275f2d753 add awesome plugin repo url 2024-09-06 19:37:24 +08:00
lloydzhou
7455978ee5 default enable artifact 2024-09-06 09:26:06 +08:00
lloydzhou
7c0acc7b77 hotfix tools empty array 2024-09-05 22:02:06 +08:00
Lloyd Zhou
f32dd69acf Merge pull request #7 from ConnectAI-E/feature/plugin-artifact
Feature/plugin artifact
2024-09-05 14:54:01 +08:00
lloydzhou
80b8f956a9 move artifacts into mask settings 2024-09-05 14:49:11 +08:00
lloydzhou
caf50b6e6c move artifacts into mask settings 2024-09-05 14:46:16 +08:00
lloydzhou
b590d0857c disable nextjs proxy, then can using dalle as plugin 2024-09-05 13:51:59 +08:00
Lloyd Zhou
982019307c Merge pull request #6 from ConnectAI-E/feature/plugin-app-cors
using tauri http api run plugin to fixed cors in App
2024-09-04 22:49:35 +08:00
lloydzhou
09aec7b22e using tauri http api run plugin to fixed cors in App 2024-09-04 21:32:22 +08:00
lloydzhou
f9a047aad4 using tauri http api run plugin to fixed cors in App 2024-09-04 21:04:13 +08:00
Lloyd Zhou
85704570f3 Merge pull request #5356 from ConnectAI-E/feature/indexdb
fix: hydrated for indexedDB
2024-09-04 19:14:18 +08:00
lloydzhou
53dcae9e9c update 2024-09-04 13:00:18 +08:00
lloydzhou
04e1ab63bb update readme 2024-09-04 11:47:42 +08:00
Dogtiti
ed9aae531e fix: hydrated 2024-09-03 20:29:01 +08:00
lloydzhou
6ab6b3dbca remove no need code 2024-09-03 20:21:37 +08:00
lloydzhou
7180ed9a60 hotfix 2024-09-03 19:56:22 +08:00
lloydzhou
0a5522d28c update 2024-09-03 19:35:36 +08:00
lloydzhou
c7bc93b32b Merge remote-tracking branch 'connectai/main' into feature/plugin 2024-09-03 19:02:40 +08:00
lloydzhou
d30351e7b0 update readme 2024-09-03 17:18:43 +08:00
Dogtiti
886ffc0af8 fix: hydrated for indexedDB 2024-09-03 17:12:48 +08:00
lloydzhou
4fdd997108 hotfix 2024-09-03 16:23:54 +08:00
lloydzhou
236736deea remove no need code 2024-09-03 15:37:23 +08:00
lloydzhou
2b317f60c8 add config auth location 2024-09-03 12:00:55 +08:00
lloydzhou
3ec67f9f47 add load from url 2024-09-03 00:45:11 +08:00
lloydzhou
6435e7a30e update readme 2024-09-02 23:42:56 +08:00
lloydzhou
078305f5ac kimi support function call 2024-09-02 21:55:17 +08:00
lloydzhou
801b62543a claude support function call 2024-09-02 21:45:47 +08:00
lloydzhou
877668b629 hotfix 2024-09-02 18:29:00 +08:00
lloydzhou
f652f73260 plugin add auth config 2024-09-02 18:11:19 +08:00
lloydzhou
b2965e1deb update 2024-08-31 00:16:47 +08:00
lloydzhou
2214689920 add gapier proxy 2024-08-30 23:51:03 +08:00
lloydzhou
9326ff9d08 ts error 2024-08-30 23:39:08 +08:00
lloydzhou
271f58d9cf stash code 2024-08-30 17:31:20 +08:00
lloydzhou
cac99e3908 add Plugin page 2024-08-30 13:02:03 +08:00
Dogtiti
97a4a910e0 Merge pull request #5335 from ConnectAI-E/fix/right-click
fix: right click
2024-08-29 20:49:31 +08:00
Dogtiti
19c7a84548 fix: right click 2024-08-29 20:48:04 +08:00
lloydzhou
571ce11e53 stash code 2024-08-29 19:55:09 +08:00
lloydzhou
d2cb984ced add processToolMessage callback 2024-08-29 17:28:15 +08:00
lloydzhou
7fc0d11931 create common function stream for fetchEventSource 2024-08-29 17:14:23 +08:00
lloydzhou
341a52a615 stash code 2024-08-29 01:35:41 +08:00
lloydzhou
d58b99d602 stash code 2024-08-29 01:00:16 +08:00
lloydzhou
f7a5f836db stash code 2024-08-29 00:56:20 +08:00
lloydzhou
d212df8b95 stash code 2024-08-29 00:39:51 +08:00
lloydzhou
f3f6dc57c3 stash code 2024-08-29 00:32:35 +08:00
lloydzhou
29b5cd9436 ts error 2024-08-29 00:21:26 +08:00
lloydzhou
f5209fc344 stash code 2024-08-28 23:58:46 +08:00
Lloyd Zhou
b6bb1673d4 Merge pull request #5324 from ConnectAI-E/feature/indexdb
feat: add indexDB
2024-08-27 18:07:20 +08:00
Dogtiti
7b6fe66f2a feat: try catch indexedDB error 2024-08-27 10:05:37 +08:00
Dogtiti
c2fc0b4979 feat: try catch indexedDB error 2024-08-27 09:57:07 +08:00
Dogtiti
0b758941a4 feat: clear indexDB 2024-08-26 21:23:21 +08:00
Dogtiti
492b55c893 feat: add indexDB 2024-08-26 21:20:07 +08:00
Dogtiti
4060e367ad feat: add indexDB 2024-08-26 21:13:35 +08:00
lloydzhou
c99cd31b6b add openapi-client-axios 2024-08-24 15:34:33 +08:00
Lloyd Zhou
718782f5b1 Merge pull request #5309 from ElricLiu/main
Update README.md
2024-08-24 12:11:46 +08:00
ElricLiu
0c3fb5b2ce Update README.md
add monica sponsored
2024-08-23 17:28:00 +08:00
mayfwl
4ec6b067e7 fix: artifact render error (#5306)
fix: artifact render error
2024-08-21 18:48:44 +08:00
Dogtiti
1748dd6a3b Merge pull request #5303 from ConnectAI-E/Modifylang
Modify View All Languages
2024-08-21 16:28:36 +08:00
mayfwl
95332e50ed Merge branch 'main' into Modifylang 2024-08-21 15:45:04 +08:00
lyf
8496980cf9 Modify View All Languages 2024-08-21 14:28:39 +08:00
Lloyd Zhou
ffe32694b0 Merge pull request #5300 from ConnectAI-E/hotfix/hide-button
Hotfix/hide button
2024-08-21 14:12:03 +08:00
lloydzhou
3d5b21154b update 2024-08-21 12:05:03 +08:00
lloydzhou
4b9697e336 fix: typescript error 2024-08-21 11:54:48 +08:00
lloydzhou
b0e9a542ba frat: add reload button 2024-08-21 11:17:00 +08:00
lloydzhou
8b67536c23 fix: 修复多余的查看全部 2024-08-21 10:28:34 +08:00
lloydzhou
cd49c12181 fix: 修复查看全部按钮导致artifacts失效 2024-08-21 10:27:37 +08:00
Lloyd Zhou
a6b14c7910 Merge pull request #5274 from Movelocity/feat/search-history
feat: add a page to search chat history
2024-08-21 00:58:01 +08:00
heweikang
e275abdb9c match the origin format 2024-08-20 19:49:47 +08:00
heweikang
09a90665d5 Merge branch 'feat/search-history' of https://github.com/Movelocity/ChatGPT-Next-Web into feat/search-history 2024-08-20 19:47:41 +08:00
heweikang
6649fbdfd0 remove an empty line 2024-08-20 19:47:36 +08:00
heweikang
64a0ffee7b Merge branch 'main' into feat/search-history 2024-08-20 19:47:03 +08:00
Hollway
b529118f31 Merge branch 'ChatGPTNextWeb:main' into feat/search-history 2024-08-20 19:46:08 +08:00
heweikang
39d7d9f13a migrate the search button to plugins discovery 2024-08-20 19:44:22 +08:00
heweikang
fcd55df969 wrap doSearch with useCallback 2024-08-20 09:45:34 +08:00
Dogtiti
1e59948358 Merge pull request #5288 from zhangjian10/main
fix: Determine if Tencent is authorized
2024-08-19 09:20:58 +08:00
zhangjian10
1102ef6e6b fix: Determine if Tencent is authorized 2024-08-18 23:47:23 +08:00
heweikang
7ce2e8f4c4 resolve a warning 2024-08-17 11:26:38 +08:00
heweikang
fd1c656bdd add all translations for SearchChat 2024-08-17 11:08:38 +08:00
heweikang
82298a760a Merge branch 'main' into feat/search-history 2024-08-17 10:15:49 +08:00
heweikang
b84bb72e07 add null check for search content 2024-08-17 10:06:56 +08:00
lloydzhou
495b321d0a Merge remote-tracking branch 'origin/main' 2024-08-16 16:35:31 +08:00
lloydzhou
8a38cdc1d7 update version 2024-08-16 16:35:13 +08:00
Lloyd Zhou
e210db7bd9 Merge pull request #5235 from ConnectAI-E/feature/access
代码折叠
2024-08-16 16:34:13 +08:00
Lloyd Zhou
f64763b74b Merge pull request #5093 from DDMeaqua/feat-i18n
feat: update i18n
2024-08-16 16:20:31 +08:00
Lloyd Zhou
e033bef544 Merge branch 'main' into feat-i18n 2024-08-16 16:13:13 +08:00
DDMeaqua
d88cc575e5 chore: update 2024-08-16 16:10:31 +08:00
heweikang
e3f499be0c hide search button text 2024-08-16 10:23:27 +08:00
Dogtiti
87325fad74 Merge pull request #5270 from DDMeaqua/main
feat: add model name to conversation page
2024-08-15 20:12:33 +08:00
Hollway
86220573b6 Update yarn.lock to match origin version 2024-08-15 17:27:14 +08:00
heweikang
65ed6b02a4 Merge branch 'main' into feat/search-history 2024-08-15 17:24:17 +08:00
heweikang
98093a1f31 make search result item easier to click 2024-08-15 17:21:39 +08:00
heweikang
00990dc195 use yarn instead of npm 2024-08-15 17:09:41 +08:00
Lloyd Zhou
122aa94c9f Merge pull request #5273 from ElricLiu/main
Update README.md
2024-08-15 14:35:48 +08:00
heweikang
3da5284a07 优化搜索算法,更新图标 2024-08-15 12:38:20 +08:00
heweikang
cd920364f8 Add page to search chat history 2024-08-14 22:28:05 +08:00
ElricLiu
e2e8a45104 Update README.md 2024-08-14 20:38:52 +08:00
DDMeaqua
fb5fc13f72 feat: add model name 2024-08-13 19:58:44 +08:00
Dogtiti
edb92f7bfb Merge pull request #5245 from MrrDrr/dalle3_add_options
dall-e-3 adds 'quality' and 'style' options
2024-08-13 15:31:18 +08:00
lyf
1980f43b9f feat(auth): xg feature 2024-08-12 16:31:12 +08:00
lyf
c3c3dd5154 feature add zhedie 2024-08-12 14:06:21 +08:00
lyf
356155fd30 feature add zhedie 2024-08-12 13:58:53 +08:00
lyf
6f75ef8f0a addcd 2024-08-10 12:45:50 +08:00
l.tingting
ca865a80dc dall-e-3 adds 'quality' and 'style' options 2024-08-10 11:09:07 +08:00
lyf
8f759d1c3e 代码折叠 2024-08-09 14:55:25 +08:00
lyf
44787637f2 tj 2024-08-08 21:07:03 +08:00
Dogtiti
cf1c8e8f2a Merge pull request #5233 from ConnectAI-E/feature/bangpt
Feature/bangpt
2024-08-08 17:27:50 +08:00
lyf
d948be2372 Merge branch 'feature/bangpt' of ssh://github.com/ConnectAI-E/ChatGPT-Next-Web into feature/bangpt 2024-08-08 16:25:49 +08:00
lyf
cc28aef625 add gpt-4o-2024-08-06 2024-08-08 16:23:40 +08:00
Dogtiti
036358de7c Merge pull request #5219 from ConnectAI-E/feature/access
修复ChatGPTNextWeb页面的无障碍问题
2024-08-08 13:59:16 +08:00
lyf
0958b9ee12 tsxiugai 2024-08-08 13:08:56 +08:00
Dogtiti
aff1d7ecd6 Merge pull request #5223 from 122cygf/main
修复拼写错误
2024-08-07 21:29:43 +08:00
lzz
42fdbd9bb8 Fix spelling errors 2024-08-07 17:22:32 +08:00
lyf
034c82e514 修改设置的无障碍 2024-08-07 13:39:23 +08:00
lyf
14ff46b5cd 解决按钮无障碍 2024-08-07 13:01:08 +08:00
lyf
c9099ca0a5 无障碍按钮和链接 2024-08-07 10:55:02 +08:00
Dogtiti
58b144b345 Merge pull request #5218 from ConnectAI-E/feature/bangpt
解决禁止gpt4时禁止gtp4o-mini问题
2024-08-07 10:45:38 +08:00
lyf
af21c57e77 ban gpt4 2024-08-07 10:15:39 +08:00
lloydzhou
624e4dbaaf update version 2024-08-06 22:41:35 +08:00
Dogtiti
9bbb4f396b Merge pull request #5181 from webws/feature/support-iflytek-spark
Feature/support iflytek spark
2024-08-06 19:32:43 +08:00
webws
b2c1644d69 feat: add support for iFLYTEK Spark API (接入讯飞星火模型) 2024-08-06 17:54:30 +08:00
Lloyd Zhou
5629f842da Merge pull request #5174 from ahzmr/main
style: Fixed an issue where the sample of the reply content was displayed out of order
2024-08-06 16:52:43 +08:00
HyiKi
f900283b09 fix: tencent InvalidParameter error
fix "Messages 中 system 角色必须位于列表的最开始"
2024-08-06 15:50:12 +08:00
HyiKi
b667eff6bd fix: baidu error_code 336006
change the summary role from system to user
2024-08-06 15:50:12 +08:00
HyiKi
54fdf40f5a fix: baidu error_code 336006 2024-08-06 15:50:12 +08:00
Lloyd Zhou
690542145d Merge pull request #5194 from HyiKi/main
fix: baidu error_code 336006
2024-08-06 13:56:44 +08:00
Lloyd Zhou
94c4cf0624 Merge pull request #5205 from ConnectAI-E/fix/azure-summary
fix: azure summary
2024-08-06 11:23:00 +08:00
Dogtiti
3da717d9fc fix: azure summary 2024-08-06 11:20:03 +08:00
HyiKi
0902efc719 Merge branch 'fix-role-error' 2024-08-06 10:53:32 +08:00
HyiKi
d7e2ee63d8 fix: tencent InvalidParameter error
fix "Messages 中 system 角色必须位于列表的最开始"
2024-08-06 10:45:25 +08:00
GH Action - Upstream Sync
7deb36ee1f Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-06 01:33:38 +00:00
GH Action - Upstream Sync
bfe4e88246 Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-06 01:00:29 +00:00
HyiKi
9ab45c3969 fix: baidu error_code 336006
change the summary role from system to user
2024-08-05 20:50:36 +08:00
Dogtiti
fec80c6c51 Merge pull request #5173 from ConnectAI-E/feature/dalle
add dalle3 model
2024-08-05 20:49:37 +08:00
Dogtiti
a6b7432358 Merge pull request #5180 from frostime/contrib-modellist
 feat: 调整模型列表,将自定义模型放在前面显示
2024-08-05 20:49:07 +08:00
frostime
3486954e07 🐛 fix(openai): 上次 commit 后 openai.ts 文件中出现类型不匹配的 bug 2024-08-05 20:26:48 +08:00
frostime
150fc84b9b feat(model): 增加 sorted 字段,并使用该字段对模型列表进行排序
1. 在 Model 和 Provider 类型中增加 sorted 字段(api.ts)
2. 默认模型在初始化的时候,自动设置默认 sorted 字段,从 1000 开始自增长(constant.ts)
3. 自定义模型更新的时候,自动分配 sorted 字段(model.ts)
2024-08-05 19:43:32 +08:00
frostime
b023a00445 🔨 refactor(model): 更改原先的实现方法,在 collect table 函数后面增加额外的 sort 处理 2024-08-05 16:37:22 +08:00
HyiKi
d0e296adf8 fix: baidu error_code 336006 2024-08-05 15:41:13 +08:00
Dogtiti
aa40015e9b Merge pull request #5190 from ConnectAI-E/feaure/hmac
reduce cloudflare functions build size
2024-08-05 14:07:55 +08:00
lloydzhou
141ce2c99a reduce cloudflare functions build size 2024-08-05 12:59:27 +08:00
lloydzhou
4a95dcb6e9 hotfix get wrong llm 2024-08-05 12:45:25 +08:00
lloydzhou
1610675c8f remove hash.js 2024-08-05 11:36:35 +08:00
GH Action - Upstream Sync
724c814bfe Merge branch 'main' of https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 2024-08-04 01:04:56 +00:00
Dogtiti
764c0cb865 Merge pull request #5179 from frostime/contrib-font
 feat(config): Config the font-family of chat content
2024-08-03 21:04:43 +08:00
frostime
8a4b8a84d6 feat: 调整模型列表,将自定义模型放在前面显示 2024-08-03 17:16:05 +08:00
frostime
8ec6acc55a 🎨 i18n: add locale about "font family config"
Note: The language is translated by GPT
2024-08-03 16:24:51 +08:00
frostime
b6a022b0ef feat(config): Set markdown's font-family 2024-08-03 16:04:20 +08:00
李超
716899c030 fix: Fixed the issue that WebDAV synchronization could not check the status and failed during the first backup 2024-08-03 12:40:48 +08:00
Dogtiti
d9e407fd2b Merge pull request #5175 from frostime/upstream-main
 feat: 为命令前缀( `:` )增加对中文符号 `:`的支持
2024-08-03 10:37:51 +08:00
frostime
deb140de73 feat: 为命令前缀( : )支持中文符号 2024-08-02 22:38:31 +08:00
织梦人
3c1e5e7978 Merge branch 'ChatGPTNextWeb:main' into main 2024-08-02 22:26:28 +08:00
Dogtiti
4a8e85c28a fix: empty response 2024-08-02 22:16:08 +08:00
李超
8498cadae8 fix: Fixed an issue where the sample of the reply content was displayed out of order 2024-08-02 21:06:03 +08:00
lloydzhou
8c83fe23a1 using b64_json for dall-e-3 2024-08-02 20:58:21 +08:00
Lloyd Zhou
a8c65e3d27 Merge pull request #5126 from DDDDD12138/fix-watch-mask
fix: replace '&' with concurrently for cross-platform compatibility
2024-08-02 19:22:57 +08:00
Lloyd Zhou
324d30bef9 Merge pull request #5136 from frostime/contrib-txtcode
 feat(markdown): 对纯文本的代码块内容进行折行处理
2024-08-02 19:22:13 +08:00
lloydzhou
46cb48023e fix typescript error 2024-08-02 18:50:48 +08:00
lloydzhou
1c24ca58c7 add dalle3 model 2024-08-02 18:03:19 +08:00
Lloyd Zhou
9193a9a0e0 Merge pull request #5147 from ConnectAI-E/fix-locales
fix: locales => Locale
2024-08-02 18:02:13 +08:00
Lloyd Zhou
957244ba2e Merge pull request #5154 from ConnectAI-E/fix-sidebar-style
fix: sidebar title style
2024-08-02 18:01:51 +08:00
lloydzhou
ac599aa47c add dalle3 model 2024-08-02 18:00:42 +08:00
Dogtiti
67a90ffb76 Merge pull request #5171 from ConnectAI-E/feature/tencent
Feature/tencent
2024-08-02 16:43:00 +08:00
lloydzhou
feaa6f9bf0 Merge remote-tracking branch 'connectai/feature/tencent' into feature/tencent 2024-08-02 15:56:25 +08:00
lloydzhou
753bf3b924 hotfix for cf deploy error #5168 2024-08-02 15:55:26 +08:00
Dogtiti
b3219f57c8 Merge pull request #5157 from ConnectAI-E/feature/tencent
Feature/tencent
2024-08-01 23:56:27 +08:00
Dogtiti
a17df037af feat: add lodash-es 2024-08-01 23:54:14 +08:00
lloydzhou
dfc36e5210 update 2024-08-01 19:02:40 +08:00
lloydzhou
c359b92ddc update 2024-08-01 18:58:07 +08:00
lloydzhou
e1d6131f13 update 2024-08-01 16:49:55 +08:00
lloydzhou
6a0bda00f5 merge main 2024-08-01 15:39:54 +08:00
lloydzhou
f85ec95877 hotfix for tencent sign 2024-08-01 15:33:48 +08:00
lloydzhou
a024980c03 revert package.json 2024-08-01 13:21:36 +08:00
lloydzhou
fd9e94e078 merge main 2024-08-01 13:18:33 +08:00
Dogtiti
f6a6c51d15 Merge pull request #5156 from ConnectAI-E/feature/moonshot
add moonshot api
2024-08-01 12:28:10 +08:00
lloydzhou
966db1e4be add moonshot settings 2024-08-01 12:03:22 +08:00
lloydzhou
b8bbc37b8e merge main 2024-08-01 11:57:23 +08:00
lloydzhou
40cbabc330 add moonshot api 2024-08-01 11:55:22 +08:00
wuzhiqing
04a4e1b39a fix: replace '&' with concurrently for cross-platform compatibility
- Used `concurrently` to run parallel commands in package.json scripts
- Added `concurrently` as a devDependency in package.json
2024-08-01 10:05:21 +08:00
Dogtiti
99f3160aa2 fix: sidebar title style 2024-07-31 22:10:24 +08:00
Dogtiti
8cb72d8452 fix: locales => Locale 2024-07-30 21:25:33 +08:00
Dogtiti
c9eb9f3eda Merge pull request #5141 from ConnectAI-E/hotfix-google-api-in-app
hotfix for using google api in app
2024-07-30 15:42:30 +08:00
lloydzhou
64c3dcd732 hotfix for using google api in app 2024-07-30 12:03:06 +08:00
frostime
d49ececcc5 feat(markdown): wrap the plain text code block 2024-07-29 19:10:00 +08:00
Lloyd Zhou
90e1fadb1e Merge pull request #5124 from ElricLiu/main
Update README.md
2024-07-29 14:58:07 +08:00
ElricLiu
071391ddff Update README.md 2024-07-29 14:57:46 +08:00
ElricLiu
d70d46b4d5 Update README.md 2024-07-29 14:56:39 +08:00
ElricLiu
3ef596b215 Update README.md
Update redame,add Artifacts & SD
2024-07-29 14:42:30 +08:00
Lloyd Zhou
35c5518668 Merge pull request #5110 from ConnectAI-E/fix/owner
chore: update owner
2024-07-26 19:28:45 +08:00
lloydzhou
8b513537b7 release v2.14.0 2024-07-26 19:24:16 +08:00
ElricLiu
b27f394995 Merge pull request #5092 from ConnectAI-E/feature-artifacts
[Artifacts] add preview html code
2024-07-26 17:28:49 +08:00
Dogtiti
3f9f556e1c fix: iframe bg 2024-07-26 17:06:10 +08:00
Dogtiti
1772d5c4b6 chore: update owner 2024-07-26 16:00:25 +08:00
Dogtiti
715d1dc02f fix: default enable artifacts 2024-07-26 15:55:01 +08:00
Dogtiti
6737f016f5 chore: artifact => artifacts 2024-07-26 15:50:26 +08:00
Dogtiti
f2d2622172 fix: uploading loading 2024-07-26 13:49:15 +08:00
Dogtiti
72d6f97024 fix: ts error 2024-07-26 11:21:51 +08:00
Lloyd Zhou
a0f0b4ff9e Merge pull request #5 from ConnectAI-E/feature/artifacts-style
Feature/artifacts style
2024-07-25 23:36:34 +08:00
Dogtiti
c27ef6ffbf feat: artifacts style 2024-07-25 23:29:29 +08:00
Lloyd Zhou
f5499ff699 Merge pull request #5071 from ZTH7/main
Fix defaultModel undefined error
2024-07-25 21:02:39 +08:00
Mr. Z
c4334d4e5f Update model.ts 2024-07-25 20:03:54 +08:00
Dogtiti
51e8f0440d Merge branch 'feature-artifacts' of https://github.com/ConnectAI-E/ChatGPT-Next-Web into feature/artifacts-style 2024-07-25 19:43:20 +08:00
lloydzhou
5ec0311f84 fix typescript error 2024-07-25 19:38:18 +08:00
lloydzhou
556d563ba0 update 2024-07-25 19:31:16 +08:00
lloydzhou
6a083b24c4 fix typescript error 2024-07-25 19:22:18 +08:00
lloydzhou
825929fdc8 merge main 2024-07-25 19:18:45 +08:00
Lloyd Zhou
941a03ed6c Merge pull request #4983 from OpenAI-Next/dev-sd
[Feature] Stable Diffusion
2024-07-25 19:13:37 +08:00
Lloyd Zhou
cf63619182 Merge pull request #5103 from ConnectAI-E/hotfix-cache-upload-image
hotfix cache upload image
2024-07-25 19:01:10 +08:00
Mr. Z
5c04d3c5ea Change method 2024-07-25 17:59:15 +08:00
Mr. Z
46a47db2d8 Merge branch 'ChatGPTNextWeb:main' into main 2024-07-25 17:55:53 +08:00
Dogtiti
21ef9a4567 feat: artifacts style 2024-07-25 17:37:21 +08:00
lloydzhou
6f0846b2af hotfix cache upload image 2024-07-25 17:26:16 +08:00
lloydzhou
ecd78b3bdd hotfix cache upload image 2024-07-25 17:23:21 +08:00
lloydzhou
d8afd1af88 add expiration_ttl for kv storage 2024-07-25 16:56:08 +08:00
lloydzhou
7c1bc1f1a1 hotfix: auto set height 2024-07-25 15:27:44 +08:00
lloydzhou
763fc89b29 add fullscreen button on artifact component 2024-07-25 15:10:17 +08:00
lloydzhou
47b33f2b17 hotfix: auto set height 2024-07-25 14:15:16 +08:00
lloydzhou
9f0e16b045 hotfix: ts check 2024-07-25 13:48:21 +08:00
lloydzhou
2efedb1736 update 2024-07-25 13:34:59 +08:00
lloydzhou
044116c14c add plugin selector on chat 2024-07-25 13:29:39 +08:00
lloydzhou
b4bf11d648 add loading icon when upload artifact content 2024-07-25 12:49:19 +08:00
lloydzhou
6cc0a5a1a4 remove code 2024-07-25 12:36:48 +08:00
lloydzhou
8f14de5108 hotfix: ts check 2024-07-25 12:34:35 +08:00
lloydzhou
8f6e5d73a2 hotfix: can send sd task in client 2024-07-25 12:31:30 +08:00
lloydzhou
ab9f5382b2 fix typescript 2024-07-24 20:51:33 +08:00
Dogtiti
fd441d9303 feat: discovery icon 2024-07-24 20:41:41 +08:00
lloydzhou
e31bec3aff save artifact content to cloudflare workers kv 2024-07-24 20:36:11 +08:00
Dogtiti
2a1c05a028 fix: bugs 2024-07-24 20:04:22 +08:00
lloydzhou
421bf33c0e save artifact content to cloudflare workers kv 2024-07-24 20:02:37 +08:00
DDMeaqua
9c7bacc65e chore: update 2024-07-24 10:45:17 +08:00
Dogtiti
3935c725c9 feat: sd setting 2024-07-23 22:44:09 +08:00
Dogtiti
908ee0060f chore: remove sd new 2024-07-23 22:23:34 +08:00
Dogtiti
82e6fd7bb5 feat: move sd config to store 2024-07-23 21:43:55 +08:00
Dogtiti
6b98b14179 fix: sd mobile 2024-07-23 20:24:56 +08:00
lloydzhou
1ecefd88f7 hotfix 2024-07-23 17:57:41 +08:00
lloydzhou
2e9e20ce7c auto height for html preview 2024-07-23 17:54:07 +08:00
lloydzhou
fb60fbb217 auto height for html preview 2024-07-23 17:51:44 +08:00
lloydzhou
4199e17da0 auto height for html preview 2024-07-23 17:44:15 +08:00
DDMeaqua
86b8bfcb1f feat: update i18n 2024-07-23 15:21:38 +08:00
lloydzhou
dfd089132d add preview html code 2024-07-23 14:39:31 +08:00
lloydzhou
3a10f58b28 add preview html code 2024-07-23 14:33:41 +08:00
Dogtiti
9d55adbaf2 refator: sd 2024-07-23 00:51:58 +08:00
Lloyd Zhou
00be2be24f Merge pull request #5088 from ChatGPTNextWeb/revert-5080-refactor-components
Revert "feat: improve components structure"
2024-07-22 22:19:05 +08:00
Lloyd Zhou
5b126c7e52 Revert "feat: improve components structure" 2024-07-22 22:18:51 +08:00
Lloyd Zhou
1943f3b53f Merge pull request #5080 from ConnectAI-E/refactor-components
feat: improve components structure
2024-07-22 19:08:02 +08:00
Dogtiti
4a0bef9afb Merge pull request #5081 from consistent-k/main
chore: Remove useless judgment conditions
2024-07-22 17:31:00 +08:00
consistent-k
dfd2a53129 chore: Remove useless judgment conditions 2024-07-22 08:50:15 +00:00
Mr. Z
aa4e855012 Compatibility changes 2024-07-22 16:41:11 +08:00
Dogtiti
d6089e6309 fix: mask json 2024-07-22 16:33:03 +08:00
Dogtiti
038e6df8f0 feat: improve components structure 2024-07-22 16:02:45 +08:00
Mr. Z
2fd68bcac3 Fix defaultModel undefined error 2024-07-21 16:42:34 +08:00
lloydzhou
e468fecf12 update 2024-07-20 20:44:19 +08:00
lloydzhou
fc31d8e5d1 merge origin/main 2024-07-20 15:15:46 +08:00
Dogtiti
115f357a07 Merge pull request #5067 from ConnectAI-E/feature-setting-page
refactor setting page
2024-07-20 14:53:20 +08:00
lloydzhou
ac04a1cac8 resolve conflicts 2024-07-20 14:41:42 +08:00
lloydzhou
87a286ef07 refactor setting page 2024-07-20 14:36:47 +08:00
Dogtiti
622d8a4edb Merge pull request #5063 from ChenglongWang/summary_model
Change gpt summary model to gpt-4o-mini
2024-07-19 23:53:00 +08:00
Dogtiti
b44086f0dc Merge pull request #4847 from yeung66/main
add google api safety settings by Settings page
2024-07-19 23:41:04 +08:00
Chenglong Wang
0236e13187 Change gpt summary model to gpt-4o-mini. 2024-07-19 23:36:57 +08:00
YeungYeah
a3d4a7253f Merge remote-tracking branch 'source/main' 2024-07-19 21:38:25 +08:00
lloydzhou
e079f1b31a release v2.13.1 2024-07-19 18:16:05 +08:00
Dogtiti
9a78a72eb3 Merge pull request #5061 from ConnectAI-E/feature-cache-storage
using cache storage store image data #5013
2024-07-19 17:37:14 +08:00
lloydzhou
862c2e8810 hotfix for code review 2024-07-19 17:22:54 +08:00
Lloyd Zhou
12cad4c418 Merge pull request #5062 from yaway/main
Add README_JA.md
2024-07-19 17:14:32 +08:00
yaway
89b9d3a7f7 Update README_JA.md 2024-07-19 16:41:46 +08:00
yaway
57831d4880 Update and rename README_JP.md to README_JA.md 2024-07-19 16:31:12 +08:00
lloydzhou
052004d70e using compressImage when serviceWorker register error 2024-07-19 16:03:22 +08:00
lloydzhou
a765237441 reload page when sw installed. 2024-07-19 15:40:14 +08:00
lloydzhou
ac470a6d07 Merge remote-tracking branch 'connectai/feature-cache-storage' into feature-cache-storage 2024-07-19 15:39:51 +08:00
Dogtiti
7237d33be3 fix: ts type 2024-07-19 14:55:47 +08:00
lloydzhou
1610b480af remove console.log 2024-07-19 13:54:33 +08:00
lloydzhou
287fa0a39c feat: 1. using cache storage store image data; 2. get base64image before chat to api #5013 2024-07-19 13:50:10 +08:00
Dogtiti
afa1a4303b Merge pull request #5060 from DDMeaqua/main
readme_cn 补充锚点
2024-07-19 11:28:04 +08:00
DDMeaqua
28cedb1493 feat: 中文版锚点 2024-07-19 11:24:13 +08:00
Dogtiti
a280e25ee7 Merge pull request #5046 from PeterDaveHello/Add-zh-tw-prompts
Add Traditional Chinese prompts converted from Simplified Chinese version
2024-07-19 10:54:54 +08:00
Dogtiti
8464ca8931 Merge pull request #5045 from ConnectAI-E/fix/google
refactor: google
2024-07-19 10:19:04 +08:00
Dogtiti
44340f277d Merge pull request #5054 from Leizhenpeng/main
chore: update nav link to #企业版
2024-07-19 09:51:32 +08:00
Dogtiti
74bf99f9ea Merge pull request #5057 from PeterDaveHello/support-gpt-4o-mini
Add GPT-4o mini support
2024-07-19 09:48:48 +08:00
Peter Dave Hello
9caf820758 Add GPT-4o mini support
Reference:
- https://platform.openai.com/docs/models/gpt-4o-mini
- https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/
2024-07-19 03:21:40 +08:00
YeungYeah
26c2598f56 fix: fix bug in generating wrong gemini request url 2024-07-18 23:41:20 +08:00
RiverRay
6d9abf261c chore: update md 2024-07-18 23:24:29 +08:00
Lloyd Zhou
b16d0185dd Merge pull request #5033 from arloor/main
doc: fix Azure docs
2024-07-18 21:06:59 +08:00
Lloyd Zhou
ca51c2e93d Merge pull request #5035 from DDDDD12138/code-cleanup
chore: remove unused imports and correct typos
2024-07-18 21:05:52 +08:00
Lloyd Zhou
da254975cd Merge pull request #5052 from LiuElric/main
Add enterprise edition introduction
2024-07-18 21:05:03 +08:00
LiuElric
86bae6be3a Merge pull request #1 from yaway/patch-2
Update README.md
2024-07-18 17:59:45 +08:00
yaway
fc8c7ef18d Update README.md 2024-07-18 17:56:37 +08:00
LiuElric
f654629c6a Update README.md 2024-07-18 17:37:47 +08:00
LiuElric
d4a87c561a Update README.md 2024-07-18 17:34:27 +08:00
LiuElric
d8872d48b3 Update README_CN.md
Added Chinese introduction to enterprise edition
2024-07-18 17:30:56 +08:00
LiuElric
ed16c2c18d Update README.md
Add enterprise version description
2024-07-18 17:20:47 +08:00
yaway
0478a6ce3b Create README_JP.md 2024-07-18 17:20:21 +08:00
LiuElric
68b60e82ba Add ent cover 2024-07-18 17:09:32 +08:00
DDDDD12138
8edc0989e2 chore: remove unused imports and correct typos 2024-07-18 01:51:56 +08:00
Dogtiti
fbf3551bbe Merge pull request #5036 from DDDDD12138/update-env-template
chore: update .env.template to align with README.md
2024-07-17 22:55:49 +08:00
Peter Dave Hello
238d3122c4 Add Traditional Chinese prompts converted from Simplified Chinese version 2024-07-17 22:38:43 +08:00
YeungYeah
ee22fba448 Merge branch 'main' into main 2024-07-17 22:16:30 +08:00
Dogtiti
f8a2a28bff Merge pull request #5041 from DDMeaqua/feat-scrollbar-css
scrollbar width change
2024-07-17 20:09:43 +08:00
Dogtiti
b3cfaf1420 refator: google 2024-07-17 20:05:34 +08:00
Dogtiti
2e9f701bb7 Merge pull request #5040 from consistent-k/main
update constant for baidu  add ENIRE Speed  ENIRE Lite model
2024-07-17 19:05:31 +08:00
consistent-k
9aabc4ad6a update constant for baidu add ernie-tiny model 2024-07-17 10:41:31 +00:00
consistent-k
5dc731bc77 update constant for baidu add ernie-speed model 2024-07-17 09:29:43 +00:00
DDMeaqua
32d05c9855 feat: scrollbar width change 2024-07-17 17:01:50 +08:00
consistent-k
5a0d0c0b75 update constant for baidu add ernie-speed model 2024-07-17 08:54:53 +00:00
wuzhiqing
17d4a8fb26 chore: update .env.template to align with README.md 2024-07-17 11:12:26 +08:00
arloor
348c1a7d5f doc: fix Azure docs 2024-07-17 09:15:20 +08:00
lloydzhou
49151dabf5 hotfix 2024-07-16 23:18:34 +08:00
lloydzhou
eb7c7cdcb6 更新serviceworker逻辑 2024-07-16 16:37:55 +08:00
Dogtiti
ec95292209 Merge pull request #5025 from jiangying000/main
fix typo in app/components/chat.tsx
2024-07-16 16:13:07 +08:00
Lloyd Zhou
4b84fb328c Merge pull request #2 from OpenAI-Next/dev-sd-test
using indexdb store image data
2024-07-16 15:12:10 +08:00
jiangying
47d27c1f41 fix typo 2024-07-16 14:35:06 +08:00
lloydzhou
5267ad46da add header for service worker upload api 2024-07-16 11:51:43 +08:00
lloydzhou
94bc880b7f fixed typescript error 2024-07-16 01:45:15 +08:00
lloydzhou
bab3e0bc9b using CacheStorage to store image #5013 2024-07-16 01:19:40 +08:00
lloydzhou
b3a324b6f5 fixed typescript error 2024-07-15 21:21:50 +08:00
lloydzhou
a1117cd4ee save blob to indexeddb instead of base64 image string 2024-07-15 20:47:49 +08:00
lloydzhou
6ece818d69 remove no need code 2024-07-15 20:28:55 +08:00
lloydzhou
5df09d5e2a move code to utils/file 2024-07-15 20:26:03 +08:00
lloydzhou
33450ce429 move code to utils/file 2024-07-15 20:09:21 +08:00
lloydzhou
e2f0206d88 update using indexdb read sd image data 2024-07-15 19:29:25 +08:00
licoy
3767b2c7f9 test 2024-07-15 15:29:56 +08:00
Dogtiti
1779f1f3da Merge pull request #4984 from RubuJam/chore/pr
chore: add chinese template
2024-07-15 11:51:23 +08:00
Rubu Jam
a0290b0c1b chore: Modify title and labels 2024-07-12 14:52:07 +00:00
licoy
dd1030139b fix: sd image preview modal size 2024-07-12 15:20:09 +08:00
Rubu Jam
30ca2117bb chore: add chinese template 2024-07-12 07:00:53 +00:00
Rubu Jam
89024a8dc8 chore: add PR template 2024-07-12 06:28:21 +00:00
licoy
d61cb98ac7 Merge remote-tracking branch 'origin/dev-sd' into dev-sd 2024-07-12 10:33:58 +08:00
licoy
a7ceb61e27 pref: remove console 2024-07-12 10:33:43 +08:00
licoy
74b915a790 fix: sd3 model default select 2024-07-12 10:32:06 +08:00
lloydzhou
01ea690421 remove code 2024-07-11 18:35:44 +08:00
lloydzhou
17cc9284a0 add config in readme 2024-07-11 15:35:36 +08:00
lloydzhou
498d0f0b8b merge main 2024-07-11 15:29:47 +08:00
licoy
2b0153807c feat: Add Stability API server relay sending 2024-07-09 09:50:04 +08:00
Dogtiti
d726c71141 wip: tencent 2024-07-07 14:43:28 +08:00
licoy
a16725ac17 feat: Improve SD list data and API integration 2024-07-03 15:37:34 +08:00
licoy
54401162bd fix: model version field name 2024-07-02 15:31:30 +08:00
licoy
7fde9327a2 feat: Improve the data input and submission acquisition of SD parameter panel 2024-07-02 15:19:44 +08:00
licoy
bbbf59c74a Improve the Stability parameter control panel 2024-07-02 10:24:19 +08:00
licoy
34034be0e3 hide new chat button on sd page 2024-06-27 16:13:51 +08:00
licoy
d21481173e feat: add SD page switching 2024-06-27 16:06:15 +08:00
licoy
fa6ebadc7b feat: add plugin entry selection 2024-06-27 15:35:16 +08:00
licoy
a51fb24f36 fix ts error 2024-06-27 15:13:45 +08:00
YeungYeah
74986803db feat: add google api safety setting 2024-06-15 12:09:58 +08:00
YeungYeah
24bf7950d8 chore: set the google safety setting to lowest 2024-06-12 21:59:28 +08:00
125 changed files with 14743 additions and 3126 deletions

View File

@@ -1,21 +1,20 @@
# Your openai api key. (required)
OPENAI_API_KEY=sk-xxxx
# Access password, separated by comma. (optional)
CODE=your-password
# You can start service behind a proxy
# You can start service behind a proxy. (optional)
PROXY_URL=http://localhost:7890
# (optional)
# Default: Empty
# Googel Gemini Pro API key, set if you want to use Google Gemini Pro API.
# Google Gemini Pro API key, set if you want to use Google Gemini Pro API.
GOOGLE_API_KEY=
# (optional)
# Default: https://generativelanguage.googleapis.com/
# Googel Gemini Pro API url without pathname, set if you want to customize Google Gemini Pro API url.
# Google Gemini Pro API url without pathname, set if you want to customize Google Gemini Pro API url.
GOOGLE_URL=
# Override openai api request base url. (optional)
@@ -47,6 +46,15 @@ ENABLE_BALANCE_QUERY=
# If you want to disable parse settings from url, set this value to 1.
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=
@@ -54,8 +62,6 @@ ANTHROPIC_API_KEY=
### anthropic claude Api version. (optional)
ANTHROPIC_API_VERSION=
### anthropic claude Api url (optional)
ANTHROPIC_URL=

80
.github/ISSUE_TEMPLATE/1_bug_report.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
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.

View File

@@ -0,0 +1,80 @@
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: 如果您的问题需要进一步说明,或者您遇到的问题无法在一个简单的示例中复现,请在这里添加更多信息。

View File

@@ -0,0 +1,21 @@
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.

View File

@@ -0,0 +1,21 @@
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: 在这里添加关于问题的任何其他背景信息。

View File

@@ -1,146 +0,0 @@
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

View File

@@ -1,53 +0,0 @@
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 Normal file
View File

@@ -0,0 +1,28 @@
#### 💻 变更类型 | 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 dont 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.
-->

2
.gitignore vendored
View File

@@ -44,3 +44,5 @@ dev
*.key
*.key.pub
masks.json

View File

@@ -1,5 +1,8 @@
<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>
@@ -14,9 +17,9 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[![MacOS][MacOS-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) / [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) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
[网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
[网页版](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)
[web-url]: https://app.nextchat.dev/
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
@@ -25,16 +28,40 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
[![Deploy with Vercel](https://vercel.com/button)](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://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)
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/ZBUEFA)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
![cover](./docs/images/cover.png)
[<img src="https://github.com/user-attachments/assets/903482d4-3e87-4134-9af1-f2588fa90659" height="60" width="288" >](https://monica.im/?utm=nxcrp)
</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
- **Deploy for free with one-click** on Vercel in under 1 minute
@@ -49,6 +76,12 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
- 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
<div align="center">
![主界面](./docs/images/cover.png)
</div>
## Roadmap
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
@@ -57,10 +90,15 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
- [x] Share as image, share to ShareGPT [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
- [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.
- [ ] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
- [x] Artifacts: Easily preview, copy and share generated content/webpages through a separate window [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092)
- [x] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353)
- [x] network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353)
- [ ] local knowledge base
## What's New
- 🚀 v2.15.0 Now supports Plugins! Read this: [NextChat-Awesome-Plugins](https://github.com/ChatGPTNextWeb/NextChat-Awesome-Plugins)
- 🚀 v2.14.0 Now supports Artifacts & SD
- 🚀 v2.10.1 support Google Gemini Pro model.
- 🚀 v2.9.11 you can use azure endpoint now.
- 🚀 v2.8 now we have a client that runs across all platforms!
@@ -89,15 +127,21 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
- [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)
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
- [x] Artifacts: 通过独立窗口,轻松预览、复制和分享生成的内容/可交互网页 [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092)
- [x] 插件机制,支持`联网搜索``计算器`、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353)
- [x] 支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353)
- [ ] 本地知识库
## 最新动态
- 🚀 v2.15.0 现在支持插件功能了!了解更多:[NextChat-Awesome-Plugins](https://github.com/ChatGPTNextWeb/NextChat-Awesome-Plugins)
- 🚀 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)。
- 💡 想要更方便地随时随地使用本项目可以试下这款桌面插件https://github.com/mushan0x0/AI0x0.com
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
- 🚀 v2.8 发布了横跨 Linux/Windows/MacOS 的体积极小的客户端。
- 🚀 v2.9.11 现在可以使用自定义 Azure 服务了。
## Get Started
@@ -180,8 +224,7 @@ Specify OpenAI organization ID.
### `AZURE_URL` (optional)
> Example: https://{azure-resource-url}/openai/deployments/{deploy-name}
> if you config deployment name in `CUSTOM_MODELS`, you can remove `{deploy-name}` in `AZURE_URL`
> Example: https://{azure-resource-url}/openai
Azure deploy url.
@@ -241,6 +284,18 @@ Alibaba Cloud Api Key.
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)
> Default: Empty
@@ -276,6 +331,7 @@ 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.
@@ -295,6 +351,14 @@ You can use this option if you want to increase the number of webdav service add
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
NodeJS >= 18, Docker >= 20

View File

@@ -1,22 +1,34 @@
<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>
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
[企业版](#%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)
[![Deploy with Vercel](https://vercel.com/button)](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)
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/ZBUEFA)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
![主界面](./docs/images/cover.png)
[<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)
</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);
@@ -25,6 +37,12 @@
3. 部署完毕后,即可开始使用;
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain)Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
<div align="center">
![主界面](./docs/images/cover.png)
</div>
## 保持更新
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
@@ -94,8 +112,7 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
### `AZURE_URL` (可选)
> 形如https://{azure-resource-url}/openai/deployments/{deploy-name}
> 如果你已经在`CUSTOM_MODELS`中参考`displayName`的方式配置了{deploy-name},那么可以从`AZURE_URL`中移除`{deploy-name}`
> 形如https://{azure-resource-url}/openai
Azure 部署地址。
@@ -155,6 +172,20 @@ ByteDance Api Url.
阿里云千问Api Url.
### `IFLYTEK_URL` (可选)
讯飞星火Api Url.
### `IFLYTEK_API_KEY` (可选)
讯飞星火Api Key.
### `IFLYTEK_API_SECRET` (可选)
讯飞星火Api Secret.
### `HIDE_USER_API_KEY` (可选)
如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。
@@ -186,7 +217,8 @@ ByteDance Api Url.
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
在Azure的模式下支持使用`modelName@azure=deploymentName`的方式配置模型名称和部署名称(deploy-name)
> 示例:`+gpt-3.5-turbo@azure=gpt35`这个配置会在模型列表显示一个`gpt35(Azure)`的选项
> 示例:`+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)`的选项
@@ -200,6 +232,15 @@ ByteDance Api Url.
自定义默认的 template用于初始化『设置』中的『用户输入预处理』配置项
### `STABILITY_API_KEY` (optional)
Stability API密钥
### `STABILITY_URL` (optional)
自定义的Stability API请求地址
## 开发
点击下方按钮,开始二次开发:

310
README_JA.md Normal file
View File

@@ -0,0 +1,310 @@
<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. 右側のボタンをクリックしてデプロイを開始:
[![Deploy with Vercel](https://vercel.com/button)](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">
![メインインターフェース](./docs/images/cover.png)
</div>
## 更新を維持する
もし上記の手順に従ってワンクリックでプロジェクトをデプロイした場合、「更新があります」というメッセージが常に表示されることがあります。これは、Vercel がデフォルトで新しいプロジェクトを作成するためで、本プロジェクトを fork していないことが原因です。そのため、正しく更新を検出できません。
以下の手順に従って再デプロイすることをお勧めします:
- 元のリポジトリを削除する
- ページ右上の fork ボタンを使って、本プロジェクトを fork する
- Vercel で再度選択してデプロイする、[詳細な手順はこちらを参照してください](./docs/vercel-ja.md)。
### 自動更新を開く
> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
![自動更新](./docs/images/enable-actions.jpg)
![自動更新を有効にする](./docs/images/enable-actions-sync.jpg)
### 手動でコードを更新する
手動で即座に更新したい場合は、[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` (オプション)
『設定』の『ユーザー入力前処理』の初期設定に使用するテンプレートをカスタマイズします。
## 開発
下のボタンをクリックして二次開発を開始してください:
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](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 バージョンは最新バージョンより 12 日遅れることが多いため、デプロイ後に「更新があります」の通知が出続けることがありますが、正常です。
```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/)

View File

@@ -0,0 +1,70 @@
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";
import { handle as proxyHandler } from "../../proxy";
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 });
case ApiPath.OpenAI:
return openaiHandler(req, { params });
default:
return proxyHandler(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",
];

View File

@@ -14,7 +14,7 @@ import type { RequestPayload } from "@/app/client/platforms/openai";
const serverConfig = getServerSideConfig();
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -40,30 +40,6 @@ 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",
];
async function request(req: NextRequest) {
const controller = new AbortController();

View File

@@ -9,13 +9,13 @@ import {
} from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth";
import { auth } from "./auth";
import { isModelAvailableInServer } from "@/app/utils/model";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -56,30 +56,6 @@ 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();
async function request(req: NextRequest) {

View File

@@ -0,0 +1,73 @@
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";

View File

@@ -67,6 +67,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
let systemApiKey: string | undefined;
switch (modelProvider) {
case ModelProvider.Stability:
systemApiKey = serverConfig.stabilityApiKey;
break;
case ModelProvider.GeminiPro:
systemApiKey = serverConfig.googleApiKey;
break;
@@ -82,6 +85,13 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
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:
default:
if (req.nextUrl.pathname.includes("azure/deployments")) {

View File

@@ -2,10 +2,10 @@ 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";
import { auth } from "./auth";
import { requestOpenai } from "./common";
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -31,27 +31,3 @@ async function handle(
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",
];

View File

@@ -14,7 +14,7 @@ import { getAccessToken } from "@/app/utils/baidu";
const serverConfig = getServerSideConfig();
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -52,30 +52,6 @@ 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",
];
async function request(req: NextRequest) {
const controller = new AbortController();

View File

@@ -12,7 +12,7 @@ import { isModelAvailableInServer } from "@/app/utils/model";
const serverConfig = getServerSideConfig();
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -38,30 +38,6 @@ 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",
];
async function request(req: NextRequest) {
const controller = new AbortController();

View File

@@ -32,10 +32,7 @@ export async function requestOpenai(req: NextRequest) {
authHeaderName = "Authorization";
}
let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
"/api/openai/",
"",
);
let path = `${req.nextUrl.pathname}`.replaceAll("/api/openai/", "");
let baseUrl =
(isAzure ? serverConfig.azureUrl : serverConfig.baseUrl) || OPENAI_BASE_URL;

View File

@@ -1,11 +1,19 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth";
import { auth } from "./auth";
import { getServerSideConfig } from "@/app/config/server";
import { GEMINI_BASE_URL, Google, ModelProvider } from "@/app/constant";
import {
ApiPath,
GEMINI_BASE_URL,
Google,
ModelProvider,
} from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
async function handle(
const serverConfig = getServerSideConfig();
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
{ params }: { params: { provider: string; path: string[] } },
) {
console.log("[Google Route] params ", params);
@@ -13,32 +21,6 @@ async function handle(
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);
if (authResult.error) {
return NextResponse.json(authResult, {
@@ -49,9 +31,9 @@ async function handle(
const bearToken = req.headers.get("Authorization") ?? "";
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
const key = token ? token : serverConfig.googleApiKey;
const apiKey = token ? token : serverConfig.googleApiKey;
if (!key) {
if (!apiKey) {
return NextResponse.json(
{
error: true,
@@ -62,10 +44,63 @@ async function handle(
},
);
}
try {
const response = await request(req, apiKey);
return response;
} catch (e) {
console.error("[Google] ", e);
return NextResponse.json(prettyObject(e));
}
}
const fetchUrl = `${baseUrl}/${path}?key=${key}${
req?.nextUrl?.searchParams?.get("alt") == "sse" ? "&alt=sse" : ""
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",
];
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 = {
headers: {
"Content-Type": "application/json",
@@ -97,22 +132,3 @@ async function handle(
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",
];

131
app/api/iflytek.ts Normal file
View File

@@ -0,0 +1,131 @@
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);
}
}

130
app/api/moonshot.ts Normal file
View File

@@ -0,0 +1,130 @@
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);
}
}

View File

@@ -3,8 +3,8 @@ import { getServerSideConfig } from "@/app/config/server";
import { ModelProvider, OpenaiPath } from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth";
import { requestOpenai } from "../../common";
import { auth } from "./auth";
import { requestOpenai } from "./common";
const ALLOWD_PATH = new Set(Object.values(OpenaiPath));
@@ -13,14 +13,14 @@ function getModels(remoteModelRes: OpenAIListModelResponse) {
if (config.disableGPT4) {
remoteModelRes.data = remoteModelRes.data.filter(
(m) => !m.id.startsWith("gpt-4"),
(m) => !m.id.startsWith("gpt-4") || m.id.startsWith("gpt-4o-mini"),
);
}
return remoteModelRes;
}
async function handle(
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
@@ -70,27 +70,3 @@ async function handle(
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",
];

75
app/api/proxy.ts Normal file
View File

@@ -0,0 +1,75 @@
import { NextRequest, NextResponse } from "next/server";
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
console.log("[Proxy Route] params ", params);
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
// remove path params from searchParams
req.nextUrl.searchParams.delete("path");
req.nextUrl.searchParams.delete("provider");
const subpath = params.path.join("/");
const fetchUrl = `${req.headers.get(
"x-base-url",
)}/${subpath}?${req.nextUrl.searchParams.toString()}`;
const skipHeaders = ["connection", "host", "origin", "referer", "cookie"];
const headers = new Headers(
Array.from(req.headers.entries()).filter((item) => {
if (
item[0].indexOf("x-") > -1 ||
item[0].indexOf("sec-") > -1 ||
skipHeaders.includes(item[0])
) {
return false;
}
return true;
}),
);
const controller = new AbortController();
const fetchOptions: RequestInit = {
headers,
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,
};
const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);
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");
// The latest version of the OpenAI API forced the content-encoding to be "br" in json response
// So if the streaming is disabled, we need to remove the content-encoding header
// Because Vercel uses gzip to compress the response, if we don't remove the content-encoding header
// The browser will try to decode the response with brotli and fail
newHeaders.delete("content-encoding");
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: newHeaders,
});
} finally {
clearTimeout(timeoutId);
}
}

99
app/api/stability.ts Normal file
View File

@@ -0,0 +1,99 @@
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);
}
}

124
app/api/tencent/route.ts Normal file
View File

@@ -0,0 +1,124 @@
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);
}
}

View File

@@ -29,6 +29,7 @@ async function handle(
const requestUrl = new URL(req.url);
let endpoint = requestUrl.searchParams.get("endpoint");
let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method;
// Validate the endpoint to prevent potential SSRF attacks
if (
@@ -37,9 +38,13 @@ async function handle(
const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
const normalizedEndpoint = normalizeUrl(endpoint as string);
return normalizedEndpoint &&
return (
normalizedEndpoint &&
normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
normalizedEndpoint.pathname.startsWith(normalizedAllowedEndpoint.pathname);
normalizedEndpoint.pathname.startsWith(
normalizedAllowedEndpoint.pathname,
)
);
})
) {
return NextResponse.json(
@@ -61,7 +66,11 @@ async function handle(
const targetPath = `${endpoint}${endpointPath}`;
// only allow MKCOL, GET, PUT
if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
if (
proxy_method !== "MKCOL" &&
proxy_method !== "GET" &&
proxy_method !== "PUT"
) {
return NextResponse.json(
{
error: true,
@@ -74,7 +83,7 @@ async function handle(
}
// for MKCOL request, only allow request ${folder}
if (req.method === "MKCOL" && !targetPath.endsWith(folder)) {
if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) {
return NextResponse.json(
{
error: true,
@@ -87,7 +96,7 @@ async function handle(
}
// for GET request, only allow request ending with fileName
if (req.method === "GET" && !targetPath.endsWith(fileName)) {
if (proxy_method === "GET" && !targetPath.endsWith(fileName)) {
return NextResponse.json(
{
error: true,
@@ -100,7 +109,7 @@ async function handle(
}
// for PUT request, only allow request ending with fileName
if (req.method === "PUT" && !targetPath.endsWith(fileName)) {
if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) {
return NextResponse.json(
{
error: true,
@@ -114,7 +123,7 @@ async function handle(
const targetUrl = targetPath;
const method = req.method;
const method = proxy_method || req.method;
const shouldNotHaveBody = ["get", "head"].includes(
method?.toLowerCase() ?? "",
);
@@ -139,7 +148,7 @@ async function handle(
"[Any Proxy]",
targetUrl,
{
method: req.method,
method: method,
},
{
status: fetchResult?.status,

View File

@@ -5,13 +5,22 @@ import {
ModelProvider,
ServiceProvider,
} from "../constant";
import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store";
import { ChatGPTApi } from "./platforms/openai";
import {
ChatMessageTool,
ChatMessage,
ModelType,
useAccessStore,
useChatStore,
} from "../store";
import { ChatGPTApi, DalleRequestPayload } from "./platforms/openai";
import { GeminiProApi } from "./platforms/google";
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 type MessageRole = (typeof ROLES)[number];
@@ -40,6 +49,9 @@ export interface LLMConfig {
stream?: boolean;
presence_penalty?: number;
frequency_penalty?: number;
size?: DalleRequestPayload["size"];
quality?: DalleRequestPayload["quality"];
style?: DalleRequestPayload["style"];
}
export interface ChatOptions {
@@ -50,6 +62,8 @@ export interface ChatOptions {
onFinish: (message: string) => void;
onError?: (err: Error) => void;
onController?: (controller: AbortController) => void;
onBeforeTool?: (tool: ChatMessageTool) => void;
onAfterTool?: (tool: ChatMessageTool) => void;
}
export interface LLMUsage {
@@ -62,12 +76,14 @@ export interface LLMModel {
displayName?: string;
available: boolean;
provider: LLMModelProvider;
sorted: number;
}
export interface LLMModelProvider {
id: string;
providerName: string;
providerType: string;
sorted: number;
}
export abstract class LLMApi {
@@ -117,6 +133,15 @@ export class ClientApi {
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:
this.llm = new ChatGPTApi();
}
@@ -168,6 +193,19 @@ 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() {
const accessStore = useAccessStore.getState();
const chatStore = useChatStore.getState();
@@ -186,6 +224,8 @@ export function getHeaders() {
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
@@ -197,6 +237,12 @@ export function getHeaders() {
? accessStore.bytedanceApiKey
: isAlibaba
? accessStore.alibabaApiKey
: isMoonshot
? accessStore.moonshotApiKey
: isIflytek
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
: ""
: accessStore.openaiApiKey;
return {
isGoogle,
@@ -205,6 +251,8 @@ export function getHeaders() {
isBaidu,
isByteDance,
isAlibaba,
isMoonshot,
isIflytek,
apiKey,
isEnabledAccessControl,
};
@@ -214,15 +262,6 @@ export function getHeaders() {
return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
}
function getBearerToken(apiKey: string, noBearer: boolean = false): string {
return validString(apiKey)
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
: "";
}
function validString(x: string): boolean {
return x?.length > 0;
}
const {
isGoogle,
isAzure,
@@ -263,6 +302,12 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
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);
}

View File

@@ -21,7 +21,7 @@ import {
} from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
import { getMessageTextContent, isVisionModel } from "@/app/utils";
import { getMessageTextContent } from "@/app/utils";
export interface OpenAIListModelResponse {
object: string;

View File

@@ -1,9 +1,14 @@
import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant";
import { ChatOptions, getHeaders, LLMApi, MultimodalContent } from "../api";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import {
useAccessStore,
useAppConfig,
useChatStore,
usePluginStore,
ChatMessageTool,
} from "@/app/store";
import { getClientConfig } from "@/app/config/client";
import { DEFAULT_API_HOST } from "@/app/constant";
import { RequestMessage } from "@/app/typing";
import {
EventStreamContentType,
fetchEventSource,
@@ -12,7 +17,9 @@ import {
import Locale from "../../locales";
import { prettyObject } from "@/app/utils/format";
import { getMessageTextContent, isVisionModel } from "@/app/utils";
import { preProcessImageContent, stream } from "@/app/utils/chat";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
import { RequestPayload } from "./openai";
export type MultiBlockContent = {
type: "image" | "text";
@@ -93,7 +100,12 @@ export class ClaudeApi implements LLMApi {
},
};
const messages = [...options.messages];
// 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 keys = ["system", "user"];
@@ -186,112 +198,126 @@ export class ClaudeApi implements LLMApi {
const controller = new AbortController();
options.onController?.(controller);
const payload = {
method: "POST",
body: JSON.stringify(requestBody),
signal: controller.signal,
headers: {
...getHeaders(), // get common headers
"anthropic-version": accessStore.anthropicApiVersion,
// do not send `anthropicApiKey` in browser!!!
// Authorization: getAuthKey(accessStore.anthropicApiKey),
},
};
if (shouldStream) {
try {
const context = {
text: "",
finished: false,
};
const finish = () => {
if (!context.finished) {
options.onFinish(context.text);
context.finished = true;
}
};
controller.signal.onabort = finish;
fetchEventSource(path, {
...payload,
async onopen(res) {
const contentType = res.headers.get("content-type");
console.log("response content type: ", contentType);
if (contentType?.startsWith("text/plain")) {
context.text = await res.clone().text();
return finish();
}
if (
!res.ok ||
!res.headers
.get("content-type")
?.startsWith(EventStreamContentType) ||
res.status !== 200
) {
const responseTexts = [context.text];
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);
}
context.text = responseTexts.join("\n\n");
return finish();
}
},
onmessage(msg) {
let chunkJson:
| undefined
| {
type: "content_block_delta" | "content_block_stop";
delta?: {
type: "text_delta";
text: string;
};
index: number;
let index = -1;
const [tools, funcs] = usePluginStore
.getState()
.getAsTools(
useChatStore.getState().currentSession().mask?.plugin || [],
);
return stream(
path,
requestBody,
{
...getHeaders(),
"anthropic-version": accessStore.anthropicApiVersion,
},
// @ts-ignore
tools.map((tool) => ({
name: tool?.function?.name,
description: tool?.function?.description,
input_schema: tool?.function?.parameters,
})),
funcs,
controller,
// parseSSE
(text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools);
let chunkJson:
| undefined
| {
type: "content_block_delta" | "content_block_stop";
content_block?: {
type: "tool_use";
id: string;
name: string;
};
try {
chunkJson = JSON.parse(msg.data);
} catch (e) {
console.error("[Response] parse error", msg.data);
}
delta?: {
type: "text_delta" | "input_json_delta";
text?: string;
partial_json?: string;
};
index: number;
};
chunkJson = JSON.parse(text);
if (!chunkJson || chunkJson.type === "content_block_stop") {
return finish();
}
const { delta } = chunkJson;
if (delta?.text) {
context.text += delta.text;
options.onUpdate?.(context.text, delta.text);
}
},
onclose() {
finish();
},
onerror(e) {
options.onError?.(e);
throw e;
},
openWhenHidden: true,
});
} catch (e) {
console.error("failed to chat", e);
options.onError?.(e as Error);
}
if (chunkJson?.content_block?.type == "tool_use") {
index += 1;
const id = chunkJson?.content_block.id;
const name = chunkJson?.content_block.name;
runTools.push({
id,
type: "function",
function: {
name,
arguments: "",
},
});
}
if (
chunkJson?.delta?.type == "input_json_delta" &&
chunkJson?.delta?.partial_json
) {
// @ts-ignore
runTools[index]["function"]["arguments"] +=
chunkJson?.delta?.partial_json;
}
return chunkJson?.delta?.text;
},
// processToolMessage, include tool_calls message and tool call results
(
requestPayload: RequestPayload,
toolCallMessage: any,
toolCallResult: any[],
) => {
// reset index value
index = -1;
// @ts-ignore
requestPayload?.messages?.splice(
// @ts-ignore
requestPayload?.messages?.length,
0,
{
role: "assistant",
content: toolCallMessage.tool_calls.map(
(tool: ChatMessageTool) => ({
type: "tool_use",
id: tool.id,
name: tool?.function?.name,
input: tool?.function?.arguments
? JSON.parse(tool?.function?.arguments)
: {},
}),
),
},
// @ts-ignore
...toolCallResult.map((result) => ({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: result.tool_call_id,
content: result.content,
},
],
})),
);
},
options,
);
} else {
const payload = {
method: "POST",
body: JSON.stringify(requestBody),
signal: controller.signal,
headers: {
...getHeaders(), // get common headers
"anthropic-version": accessStore.anthropicApiVersion,
// do not send `anthropicApiKey` in browser!!!
// Authorization: getAuthKey(accessStore.anthropicApiKey),
},
};
try {
controller.signal.onabort = () => options.onFinish("");

View File

@@ -77,16 +77,24 @@ export class ErnieApi implements LLMApi {
async chat(options: ChatOptions) {
const messages = options.messages.map((v) => ({
role: v.role,
// "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) {
messages.unshift({
role: "user",
content: " ",
});
if (messages.at(0)?.role === "user") {
messages.splice(1, 0, {
role: "assistant",
content: " ",
});
} else {
messages.unshift({
role: "user",
content: " ",
});
}
}
const modelConfig = {

View File

@@ -1,4 +1,4 @@
import { Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
import { ApiPath, Google, REQUEST_TIMEOUT_MS } from "@/app/constant";
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { getClientConfig } from "@/app/config/client";
@@ -14,8 +14,39 @@ import {
getMessageImages,
isVisionModel,
} from "@/app/utils";
import { preProcessImageContent } from "@/app/utils/chat";
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) {
console.log("[Response] gemini-pro response: ", res);
@@ -28,7 +59,14 @@ export class GeminiProApi implements LLMApi {
async chat(options: ChatOptions): Promise<void> {
const apiClient = this;
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) }];
if (isVisionModel(options.config.model)) {
const images = getMessageImages(v);
@@ -70,6 +108,9 @@ export class GeminiProApi implements LLMApi {
// if (visionModel && messages.length > 1) {
// options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision"));
// }
const accessStore = useAccessStore.getState();
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
@@ -91,47 +132,30 @@ export class GeminiProApi implements LLMApi {
safetySettings: [
{
category: "HARM_CATEGORY_HARASSMENT",
threshold: "BLOCK_ONLY_HIGH",
threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_HATE_SPEECH",
threshold: "BLOCK_ONLY_HIGH",
threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: "BLOCK_ONLY_HIGH",
threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_ONLY_HIGH",
threshold: accessStore.googleSafetySettings,
},
],
};
const accessStore = useAccessStore.getState();
let baseUrl = "";
if (accessStore.useCustomConfig) {
baseUrl = accessStore.googleUrl;
}
const isApp = !!getClientConfig()?.isApp;
let shouldStream = !!options.config.stream;
const controller = new AbortController();
options.onController?.(controller);
try {
if (!baseUrl && isApp) {
baseUrl = DEFAULT_API_HOST + "/api/proxy/google/";
}
baseUrl = `${baseUrl}/${Google.ChatPath(modelConfig.model)}`.replaceAll(
"//",
"/",
);
if (isApp) {
baseUrl += `?key=${accessStore.googleApiKey}`;
}
// https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/Streaming_REST.ipynb
const chatPath = this.path(Google.ChatPath(modelConfig.model));
const chatPayload = {
method: "POST",
body: JSON.stringify(requestPayload),
@@ -181,10 +205,6 @@ export class GeminiProApi implements LLMApi {
controller.signal.onabort = finish;
// https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/Streaming_REST.ipynb
const chatPath =
baseUrl.replace("generateContent", "streamGenerateContent") +
(baseUrl.indexOf("?") > -1 ? "&alt=sse" : "?alt=sse");
fetchEventSource(chatPath, {
...chatPayload,
async onopen(res) {
@@ -259,7 +279,7 @@ export class GeminiProApi implements LLMApi {
openWhenHidden: true,
});
} else {
const res = await fetch(baseUrl, chatPayload);
const res = await fetch(chatPath, chatPayload);
clearTimeout(requestTimeoutId);
const resJson = await res.json();
if (resJson?.promptFeedback?.blockReason) {
@@ -285,14 +305,4 @@ export class GeminiProApi implements LLMApi {
async models(): Promise<LLMModel[]> {
return [];
}
path(path: string): string {
return "/api/google/" + path;
}
}
function ensureProperEnding(str: string) {
if (str.startsWith("[") && !str.endsWith("]")) {
return str + "]";
}
return str;
}

View File

@@ -0,0 +1,240 @@
"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 [];
}
}

View File

@@ -0,0 +1,208 @@
"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,
ChatMessageTool,
usePluginStore,
} from "@/app/store";
import { collectModelsWithDefaultModel } from "@/app/utils/model";
import { preProcessImageContent, stream } 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) {
const [tools, funcs] = usePluginStore
.getState()
.getAsTools(
useChatStore.getState().currentSession().mask?.plugin || [],
);
return stream(
chatPath,
requestPayload,
getHeaders(),
tools as any,
funcs,
controller,
// parseSSE
(text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools);
const json = JSON.parse(text);
const choices = json.choices as Array<{
delta: {
content: string;
tool_calls: ChatMessageTool[];
};
}>;
const tool_calls = choices[0]?.delta?.tool_calls;
if (tool_calls?.length > 0) {
const index = tool_calls[0]?.index;
const id = tool_calls[0]?.id;
const args = tool_calls[0]?.function?.arguments;
if (id) {
runTools.push({
id,
type: tool_calls[0]?.type,
function: {
name: tool_calls[0]?.function?.name as string,
arguments: args,
},
});
} else {
// @ts-ignore
runTools[index]["function"]["arguments"] += args;
}
}
return choices[0]?.delta?.content;
},
// processToolMessage, include tool_calls message and tool call results
(
requestPayload: RequestPayload,
toolCallMessage: any,
toolCallResult: any[],
) => {
// @ts-ignore
requestPayload?.messages?.splice(
// @ts-ignore
requestPayload?.messages?.length,
0,
toolCallMessage,
...toolCallResult,
);
},
options,
);
} 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 [];
}
}

View File

@@ -9,9 +9,22 @@ import {
REQUEST_TIMEOUT_MS,
ServiceProvider,
} from "@/app/constant";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import {
ChatMessageTool,
useAccessStore,
useAppConfig,
useChatStore,
usePluginStore,
} from "@/app/store";
import { collectModelsWithDefaultModel } from "@/app/utils/model";
import {
preProcessImageContent,
uploadImage,
base64Image2Blob,
stream,
} from "@/app/utils/chat";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
import { DalleSize, DalleQuality, DalleStyle } from "@/app/typing";
import {
ChatOptions,
@@ -32,6 +45,7 @@ import {
getMessageTextContent,
getMessageImages,
isVisionModel,
isDalle3 as _isDalle3,
} from "@/app/utils";
export interface OpenAIListModelResponse {
@@ -57,6 +71,16 @@ export interface RequestPayload {
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 {
private disableListModels = true;
@@ -99,17 +123,31 @@ export class ChatGPTApi implements LLMApi {
return cloudflareAIGatewayUrl([baseUrl, path].join("/"));
}
extractMessage(res: any) {
return res.choices?.at(0)?.message?.content ?? "";
async extractMessage(res: any) {
if (res.error) {
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) {
const visionModel = isVisionModel(options.config.model);
const messages = options.messages.map((v) => ({
role: v.role,
content: visionModel ? v.content : getMessageTextContent(v),
}));
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
@@ -119,26 +157,54 @@ export class ChatGPTApi implements LLMApi {
},
};
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.
};
let requestPayload: RequestPayload | DalleRequestPayload;
// add max_tokens to vision model
if (visionModel && modelConfig.model.includes("preview")) {
requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000);
const isDalle3 = _isDalle3(options.config.model);
if (isDalle3) {
const prompt = getMessageTextContent(
options.messages.slice(-1)?.pop() as any,
);
requestPayload = {
model: options.config.model,
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);
const shouldStream = !!options.config.stream;
const shouldStream = !isDalle3 && !!options.config.stream;
const controller = new AbortController();
options.onController?.(controller);
@@ -164,156 +230,97 @@ export class ChatGPTApi implements LLMApi {
model?.provider?.providerName === ServiceProvider.Azure,
);
chatPath = this.path(
Azure.ChatPath(
(isDalle3 ? Azure.ImagePath : Azure.ChatPath)(
(model?.displayName ?? model?.name) as string,
useCustomConfig ? useAccessStore.getState().azureApiVersion : "",
),
);
} else {
chatPath = this.path(OpenaiPath.ChatPath);
chatPath = this.path(
isDalle3 ? OpenaiPath.ImagePath : OpenaiPath.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"));
const [tools, funcs] = usePluginStore
.getState()
.getAsTools(
useChatStore.getState().currentSession().mask?.plugin || [],
);
// console.log("getAsTools", tools, funcs);
stream(
chatPath,
requestPayload,
getHeaders(),
tools as any,
funcs,
controller,
// parseSSE
(text: string, runTools: ChatMessageTool[]) => {
// console.log("parseSSE", text, runTools);
const json = JSON.parse(text);
const choices = json.choices as Array<{
delta: {
content: string;
tool_calls: ChatMessageTool[];
};
}>;
const tool_calls = choices[0]?.delta?.tool_calls;
if (tool_calls?.length > 0) {
const index = tool_calls[0]?.index;
const id = tool_calls[0]?.id;
const args = tool_calls[0]?.function?.arguments;
if (id) {
runTools.push({
id,
type: tool_calls[0]?.type,
function: {
name: tool_calls[0]?.function?.name as string,
arguments: args,
},
});
} else {
// @ts-ignore
runTools[index]["function"]["arguments"] += args;
}
}
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);
}
return choices[0]?.delta?.content;
},
// processToolMessage, include tool_calls message and tool call results
(
requestPayload: RequestPayload,
toolCallMessage: any,
toolCallResult: any[],
) => {
// @ts-ignore
requestPayload?.messages?.splice(
// @ts-ignore
requestPayload?.messages?.length,
0,
toolCallMessage,
...toolCallResult,
);
},
options,
);
} else {
const chatPayload = {
method: "POST",
body: JSON.stringify(requestPayload),
signal: controller.signal,
headers: getHeaders(),
};
controller.signal.onabort = finish;
// make a fetch request
const requestTimeoutId = setTimeout(
() => controller.abort(),
isDalle3 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
);
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;
}
if (
textmoderation &&
textmoderation.length > 0 &&
ServiceProvider.Azure
) {
const contentFilterResults =
textmoderation[0]?.content_filter_results;
console.log(
`[${ServiceProvider.Azure}] [Text Moderation] flagged categories result:`,
contentFilterResults,
);
}
} 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);
const message = await this.extractMessage(resJson);
options.onFinish(message);
}
} catch (e) {
@@ -407,13 +414,17 @@ export class ChatGPTApi implements LLMApi {
return [];
}
//由于目前 OpenAI 的 disableListModels 默认为 true所以当前实际不会运行到这场
let seq = 1000; //同 Constant.ts 中的排序保持一致
return chatModels.map((m) => ({
name: m.id,
available: true,
sorted: seq++,
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
sorted: 1,
},
}));
}

View File

@@ -0,0 +1,268 @@
"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 [];
}
}

View File

@@ -41,13 +41,16 @@ interface ChatCommands {
del?: Command;
}
export const ChatCommandPrefix = ":";
// Compatible with Chinese colon character ""
export const ChatCommandPrefix = /^[:]/;
export function useChatCommand(commands: ChatCommands = {}) {
function extract(userInput: string) {
return (
userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput
) as keyof ChatCommands;
const match = userInput.match(ChatCommandPrefix);
if (match) {
return userInput.slice(1) as keyof ChatCommands;
}
return userInput as keyof ChatCommands;
}
function search(userInput: string) {
@@ -57,7 +60,7 @@ export function useChatCommand(commands: ChatCommands = {}) {
.filter((c) => c.startsWith(input))
.map((c) => ({
title: desc[c as keyof ChatCommands],
content: ChatCommandPrefix + c,
content: ":" + c,
}));
}

View File

@@ -0,0 +1,31 @@
.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);
}

View File

@@ -0,0 +1,267 @@
import {
useEffect,
useState,
useRef,
useMemo,
forwardRef,
useImperativeHandle,
} 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 ReloadButtonIcon from "../icons/reload.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";
type HTMLPreviewProps = {
code: string;
autoHeight?: boolean;
height?: number | string;
onLoad?: (title?: string) => void;
};
export type HTMLPreviewHander = {
reload: () => void;
};
export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
function HTMLPreview(props, ref) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [frameId, setFrameId] = useState<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) {
setIframeHeight(height);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [frameId]);
useImperativeHandle(ref, () => ({
reload: () => {
setFrameId(nanoid());
},
}));
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}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
if (props.code.includes("<!DOCTYPE html>")) {
props.code.replace("<!DOCTYPE html>", "<!DOCTYPE html>" + script);
}
return script + props.code;
}, [props.code, frameId]);
const handleOnLoad = () => {
if (props?.onLoad) {
props.onLoad(title);
}
};
return (
<iframe
className={styles["artifacts-iframe"]}
key={frameId}
ref={iframeRef}
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("");
const previewRef = useRef<HTMLPreviewHander>(null);
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>
<IconButton
bordered
style={{ marginLeft: 20 }}
icon={<ReloadButtonIcon />}
shadow
onClick={() => previewRef.current?.reload()}
/>
<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}
ref={previewRef}
autoHeight={false}
height={"100%"}
onLoad={(title) => {
setFileName(title as string);
setLoading(false);
}}
/>
)}
</div>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import * as React from "react";
import styles from "./button.module.scss";
import { CSSProperties } from "react";
export type ButtonType = "primary" | "danger" | null;
@@ -16,6 +17,8 @@ export function IconButton(props: {
disabled?: boolean;
tabIndex?: number;
autoFocus?: boolean;
style?: CSSProperties;
aria?: string;
}) {
return (
<button
@@ -31,9 +34,12 @@ export function IconButton(props: {
role="button"
tabIndex={props.tabIndex}
autoFocus={props.autoFocus}
style={props.style}
aria-label={props.aria}
>
{props.icon && (
<div
aria-label={props.text || props.title}
className={
styles["icon-button-icon"] +
` ${props.type === "primary" && "no-dark"}`
@@ -44,7 +50,12 @@ export function IconButton(props: {
)}
{props.text && (
<div className={styles["icon-button-text"]}>{props.text}</div>
<div
aria-label={props.text || props.title}
className={styles["icon-button-text"]}
>
{props.text}
</div>
)}
</button>
);

View File

@@ -346,6 +346,12 @@
flex-wrap: nowrap;
}
}
.chat-model-name {
font-size: 12px;
color: var(--black);
margin-left: 6px;
}
}
.chat-message-container {
@@ -407,6 +413,21 @@
margin-top: 5px;
}
.chat-message-tools {
font-size: 12px;
color: #aaa;
line-height: 1.5;
margin-top: 5px;
.chat-message-tool {
display: flex;
align-items: end;
svg {
margin-left: 5px;
margin-right: 5px;
}
}
}
.chat-message-item {
box-sizing: border-box;
max-width: 100%;
@@ -624,4 +645,4 @@
.chat-input-send {
bottom: 30px;
}
}
}

View File

@@ -28,6 +28,7 @@ import DeleteIcon from "../icons/clear.svg";
import PinIcon from "../icons/pin.svg";
import EditIcon from "../icons/rename.svg";
import ConfirmIcon from "../icons/confirm.svg";
import CloseIcon from "../icons/close.svg";
import CancelIcon from "../icons/cancel.svg";
import ImageIcon from "../icons/image.svg";
@@ -37,6 +38,10 @@ import AutoIcon from "../icons/auto.svg";
import BottomIcon from "../icons/bottom.svg";
import StopIcon from "../icons/pause.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 {
ChatMessage,
@@ -49,6 +54,7 @@ import {
useAppConfig,
DEFAULT_TOPIC,
ModelType,
usePluginStore,
} from "../store";
import {
@@ -59,13 +65,16 @@ import {
getMessageTextContent,
getMessageImages,
isVisionModel,
isDalle3,
showPlugins,
} from "../utils";
import { compressImage } from "@/app/utils/chat";
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
import dynamic from "next/dynamic";
import { ChatControllerPool } from "../client/controller";
import { DalleSize, DalleQuality, DalleStyle } from "../typing";
import { Prompt, usePromptStore } from "../store/prompt";
import Locale from "../locales";
@@ -245,11 +254,11 @@ function useSubmitHandler() {
};
}
export type RenderPompt = Pick<Prompt, "title" | "content">;
export type RenderPrompt = Pick<Prompt, "title" | "content">;
export function PromptHints(props: {
prompts: RenderPompt[];
onPromptSelect: (prompt: RenderPompt) => void;
prompts: RenderPrompt[];
onPromptSelect: (prompt: RenderPrompt) => void;
}) {
const noPrompts = props.prompts.length === 0;
const [selectIndex, setSelectIndex] = useState(0);
@@ -338,7 +347,7 @@ function ClearContextDivider() {
);
}
function ChatAction(props: {
export function ChatAction(props: {
text: string;
icon: JSX.Element;
onClick: () => void;
@@ -432,6 +441,7 @@ export function ChatActions(props: {
const config = useAppConfig();
const navigate = useNavigate();
const chatStore = useChatStore();
const pluginStore = usePluginStore();
// switch themes
const theme = config.theme;
@@ -476,8 +486,22 @@ export function ChatActions(props: {
return model?.displayName ?? "";
}, [models, currentModel, currentProviderName]);
const [showModelSelector, setShowModelSelector] = useState(false);
const [showPluginSelector, setShowPluginSelector] = 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(() => {
const show = isVisionModel(currentModel);
setShowUploadImage(show);
@@ -620,6 +644,117 @@ export function ChatActions(props: {
}}
/>
)}
{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);
}}
/>
)}
{showPlugins(currentProviderName, currentModel) && (
<ChatAction
onClick={() => {
if (pluginStore.getAll().length == 0) {
navigate(Path.Plugins);
} else {
setShowPluginSelector(true);
}
}}
text={Locale.Plugin.Name}
icon={<PluginIcon />}
/>
)}
{showPluginSelector && (
<Selector
multiple
defaultSelectedValue={chatStore.currentSession().mask?.plugin}
items={pluginStore.getAll().map((item) => ({
title: `${item?.title}@${item?.version}`,
value: item?.id,
}))}
onClose={() => setShowPluginSelector(false)}
onSelection={(s) => {
chatStore.updateCurrentSession((session) => {
session.mask.plugin = s as string[];
});
}}
/>
)}
</div>
);
}
@@ -701,6 +836,7 @@ function _Chat() {
const session = chatStore.currentSession();
const config = useAppConfig();
const fontSize = config.fontSize;
const fontFamily = config.fontFamily;
const [showExport, setShowExport] = useState(false);
@@ -727,7 +863,7 @@ function _Chat() {
// prompt hints
const promptStore = usePromptStore();
const [promptHints, setPromptHints] = useState<RenderPompt[]>([]);
const [promptHints, setPromptHints] = useState<RenderPrompt[]>([]);
const onSearch = useDebouncedCallback(
(text: string) => {
const matchedPrompts = promptStore.search(text);
@@ -780,7 +916,7 @@ function _Chat() {
// clear search results
if (n === 0) {
setPromptHints([]);
} else if (text.startsWith(ChatCommandPrefix)) {
} else if (text.match(ChatCommandPrefix)) {
setPromptHints(chatCommands.search(text));
} else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
// check if need to trigger auto completion
@@ -812,7 +948,7 @@ function _Chat() {
setAutoScroll(true);
};
const onPromptSelect = (prompt: RenderPompt) => {
const onPromptSelect = (prompt: RenderPrompt) => {
setTimeout(() => {
setPromptHints([]);
@@ -1167,7 +1303,7 @@ function _Chat() {
...(await new Promise<string[]>((res, rej) => {
setUploading(true);
const imagesData: string[] = [];
compressImage(file, 256 * 1024)
uploadImageRemote(file)
.then((dataUrl) => {
imagesData.push(dataUrl);
setUploading(false);
@@ -1209,7 +1345,7 @@ function _Chat() {
const imagesData: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = event.target.files[i];
compressImage(file, 256 * 1024)
uploadImageRemote(file)
.then((dataUrl) => {
imagesData.push(dataUrl);
if (
@@ -1270,6 +1406,8 @@ function _Chat() {
<IconButton
icon={<RenameIcon />}
bordered
title={Locale.Chat.EditMessage.Title}
aria={Locale.Chat.EditMessage.Title}
onClick={() => setIsEditingMessage(true)}
/>
</div>
@@ -1289,6 +1427,8 @@ function _Chat() {
<IconButton
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
bordered
title={Locale.Chat.Actions.FullScreen}
aria={Locale.Chat.Actions.FullScreen}
onClick={() => {
config.update(
(config) => (config.tightBorder = !config.tightBorder),
@@ -1340,6 +1480,7 @@ function _Chat() {
<div className={styles["chat-message-edit"]}>
<IconButton
icon={<EditIcon />}
aria={Locale.Chat.Actions.Edit}
onClick={async () => {
const newMessage = await showPrompt(
Locale.Chat.Actions.Edit,
@@ -1388,6 +1529,11 @@ function _Chat() {
</>
)}
</div>
{!isUser && (
<div className={styles["chat-model-name"]}>
{message.model}
</div>
)}
{showActions && (
<div className={styles["chat-message-actions"]}>
@@ -1432,25 +1578,47 @@ function _Chat() {
</div>
)}
</div>
{showTyping && (
{message?.tools?.length == 0 && showTyping && (
<div className={styles["chat-message-status"]}>
{Locale.Chat.Typing}
</div>
)}
{/*@ts-ignore*/}
{message?.tools?.length > 0 && (
<div className={styles["chat-message-tools"]}>
{message?.tools?.map((tool) => (
<div
key={tool.id}
className={styles["chat-message-tool"]}
>
{tool.isError === false ? (
<ConfirmIcon />
) : tool.isError === true ? (
<CloseIcon />
) : (
<LoadingButtonIcon />
)}
<span>{tool?.function?.name}</span>
</div>
))}
</div>
)}
<div className={styles["chat-message-item"]}>
<Markdown
key={message.streaming ? "loading" : "done"}
content={getMessageTextContent(message)}
loading={
(message.preview || message.streaming) &&
message.content.length === 0 &&
!isUser
}
onContextMenu={(e) => onRightClick(e, message)}
// onContextMenu={(e) => onRightClick(e, message)} // hard to use
onDoubleClickCapture={() => {
if (!isMobileScreen) return;
setUserInput(getMessageTextContent(message));
}}
fontSize={fontSize}
fontFamily={fontFamily}
parentRef={scrollRef}
defaultShow={i >= messages.length - 6}
/>
@@ -1545,6 +1713,7 @@ function _Chat() {
autoFocus={autoFocus}
style={{
fontSize: config.fontSize,
fontFamily: config.fontFamily,
}}
/>
{attachImages.length != 0 && (

View File

@@ -1,3 +1,5 @@
"use client";
import React from "react";
import { IconButton } from "./button";
import GithubIcon from "../icons/github.svg";

View File

@@ -541,7 +541,7 @@ export function ImagePreviewer(props: {
<div>
<div className={styles["main-title"]}>NextChat</div>
<div className={styles["sub-title"]}>
github.com/Yidadaa/ChatGPT-Next-Web
github.com/ChatGPTNextWeb/ChatGPT-Next-Web
</div>
<div className={styles["icons"]}>
<ExportAvatar avatar={config.avatar} />
@@ -583,6 +583,7 @@ export function ImagePreviewer(props: {
<Markdown
content={getMessageTextContent(m)}
fontSize={config.fontSize}
fontFamily={config.fontFamily}
defaultShow
/>
{getMessageImages(m).length == 1 && (

View File

@@ -137,12 +137,18 @@
position: relative;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-logo {
position: absolute;
right: 0;
bottom: 18px;
display: inline-flex;
}
.sidebar-title-container {
display: inline-flex;
flex-direction: column;
}
.sidebar-title {

View File

@@ -39,6 +39,10 @@ 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, {
loading: () => <Loading noLogo />,
});
@@ -55,6 +59,21 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
loading: () => <Loading noLogo />,
});
const PluginPage = dynamic(async () => (await import("./plugin")).PluginPage, {
loading: () => <Loading noLogo />,
});
const SearchChat = dynamic(
async () => (await import("./search-chat")).SearchChatPage,
{
loading: () => <Loading noLogo />,
},
);
const Sd = dynamic(async () => (await import("./sd")).Sd, {
loading: () => <Loading noLogo />,
});
export function useSwitchTheme() {
const config = useAppConfig();
@@ -122,11 +141,23 @@ const loadAsyncGoogleFont = () => {
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() {
const config = useAppConfig();
const location = useLocation();
const isArtifact = location.pathname.includes(Path.Artifacts);
const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth;
const isSd = location.pathname === Path.Sd;
const isSdNew = location.pathname === Path.SdNew;
const isMobileScreen = useMobileScreen();
const shouldTightBorder =
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
@@ -135,34 +166,42 @@ function Screen() {
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.Plugins} element={<PluginPage />} />
<Route path={Path.SearchChat} element={<SearchChat />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</WindowContent>
</>
);
};
return (
<div
className={
styles.container +
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
getLang() === "ar" ? styles["rtl-screen"] : ""
}`
}
className={`${styles.container} ${
shouldTightBorder ? styles["tight-container"] : styles.container
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`}
>
{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>
</>
)}
{renderContent()}
</div>
);
}

View File

@@ -9,6 +9,7 @@ interface InputRangeProps {
min: string;
max: string;
step: string;
aria: string;
}
export function InputRange({
@@ -19,11 +20,13 @@ export function InputRange({
min,
max,
step,
aria,
}: InputRangeProps) {
return (
<div className={styles["input-range"] + ` ${className ?? ""}`}>
{title || value}
<input
aria-label={aria}
type="range"
title={title}
value={value}

View File

@@ -6,13 +6,21 @@ import RehypeKatex from "rehype-katex";
import RemarkGfm from "remark-gfm";
import RehypeHighlight from "rehype-highlight";
import { useRef, useState, RefObject, useEffect, useMemo } from "react";
import { copyToClipboard } from "../utils";
import { copyToClipboard, useWindowSize } from "../utils";
import mermaid from "mermaid";
import Locale from "../locales";
import LoadingIcon from "../icons/three-dots.svg";
import ReloadButtonIcon from "../icons/reload.svg";
import React from "react";
import { useDebouncedCallback } from "use-debounce";
import { showImageModal } from "./ui-lib";
import { showImageModal, FullScreen } from "./ui-lib";
import {
ArtifactsShareButton,
HTMLPreview,
HTMLPreviewHander,
} from "./artifacts";
import { useChatStore } from "../store";
import { IconButton } from "./button";
export function Mermaid(props: { code: string }) {
const ref = useRef<HTMLDivElement>(null);
@@ -62,27 +70,59 @@ export function Mermaid(props: { code: string }) {
export function PreCode(props: { children: any }) {
const ref = useRef<HTMLPreElement>(null);
const refText = ref.current?.innerText;
const previewRef = useRef<HTMLPreviewHander>(null);
const [mermaidCode, setMermaidCode] = useState("");
const [htmlCode, setHtmlCode] = useState("");
const { height } = useWindowSize();
const chatStore = useChatStore();
const session = chatStore.currentSession();
const renderMermaid = useDebouncedCallback(() => {
const renderArtifacts = useDebouncedCallback(() => {
if (!ref.current) return;
const mermaidDom = ref.current.querySelector("code.language-mermaid");
if (mermaidDom) {
setMermaidCode((mermaidDom as HTMLElement).innerText);
}
const htmlDom = ref.current.querySelector("code.language-html");
const refText = ref.current.querySelector("code")?.innerText;
if (htmlDom) {
setHtmlCode((htmlDom as HTMLElement).innerText);
} else if (refText?.startsWith("<!DOCTYPE")) {
setHtmlCode(refText);
}
}, 600);
const enableArtifacts = session.mask?.enableArtifacts !== false;
//Wrap the paragraph for plain-text
useEffect(() => {
setTimeout(renderMermaid, 1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refText]);
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";
}
});
setTimeout(renderArtifacts, 1);
}
}, []);
return (
<>
{mermaidCode.length > 0 && (
<Mermaid code={mermaidCode} key={mermaidCode} />
)}
<pre ref={ref}>
<span
className="copy-code-button"
@@ -95,6 +135,69 @@ export function PreCode(props: { children: any }) {
></span>
{props.children}
</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}
/>
<IconButton
style={{ position: "absolute", right: 120, top: 10 }}
bordered
icon={<ReloadButtonIcon />}
shadow
onClick={() => previewRef.current?.reload()}
/>
<HTMLPreview
ref={previewRef}
code={htmlCode}
autoHeight={!document.fullscreenElement}
height={!document.fullscreenElement ? 600 : height}
/>
</FullScreen>
)}
</>
);
}
function CustomCode(props: { children: any; className?: string }) {
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
className={props?.className}
ref={ref}
style={{
maxHeight: collapsed ? "400px" : "none",
overflowY: "hidden",
}}
>
{props.children}
</code>
{showToggle && collapsed && (
<div
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
>
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
</div>
)}
</>
);
}
@@ -154,6 +257,7 @@ function _MarkDownContent(props: { content: string }) {
]}
components={{
pre: PreCode,
code: CustomCode,
p: (pProps) => <p {...pProps} dir="auto" />,
a: (aProps) => {
const href = aProps.href || "";
@@ -175,6 +279,7 @@ export function Markdown(
content: string;
loading?: boolean;
fontSize?: number;
fontFamily?: string;
parentRef?: RefObject<HTMLDivElement>;
defaultShow?: boolean;
} & React.DOMAttributes<HTMLDivElement>,
@@ -186,6 +291,7 @@ export function Markdown(
className="markdown-body"
style={{
fontSize: `${props.fontSize ?? 14}px`,
fontFamily: props.fontFamily || "inherit",
}}
ref={mdRef}
onContextMenu={props.onContextMenu}

View File

@@ -127,6 +127,8 @@ export function MaskConfig(props: {
onClose={() => setShowPicker(false)}
>
<div
tabIndex={0}
aria-label={Locale.Mask.Config.Avatar}
onClick={() => setShowPicker(true)}
style={{ cursor: "pointer" }}
>
@@ -139,6 +141,7 @@ export function MaskConfig(props: {
</ListItem>
<ListItem title={Locale.Mask.Config.Name}>
<input
aria-label={Locale.Mask.Config.Name}
type="text"
value={props.mask.name}
onInput={(e) =>
@@ -153,6 +156,7 @@ export function MaskConfig(props: {
subTitle={Locale.Mask.Config.HideContext.SubTitle}
>
<input
aria-label={Locale.Mask.Config.HideContext.Title}
type="checkbox"
checked={props.mask.hideContext}
onChange={(e) => {
@@ -163,12 +167,29 @@ export function MaskConfig(props: {
></input>
</ListItem>
<ListItem
title={Locale.Mask.Config.Artifacts.Title}
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
>
<input
aria-label={Locale.Mask.Config.Artifacts.Title}
type="checkbox"
checked={props.mask.enableArtifacts !== false}
onChange={(e) => {
props.updateMask((mask) => {
mask.enableArtifacts = e.currentTarget.checked;
});
}}
></input>
</ListItem>
{!props.shouldSyncFromGlobal ? (
<ListItem
title={Locale.Mask.Config.Share.Title}
subTitle={Locale.Mask.Config.Share.SubTitle}
>
<IconButton
aria={Locale.Mask.Config.Share.Title}
icon={<CopyIcon />}
text={Locale.Mask.Config.Share.Action}
onClick={copyMaskLink}
@@ -182,6 +203,7 @@ export function MaskConfig(props: {
subTitle={Locale.Mask.Config.Sync.SubTitle}
>
<input
aria-label={Locale.Mask.Config.Sync.Title}
type="checkbox"
checked={props.mask.syncGlobalConfig}
onChange={async (e) => {

View File

@@ -17,6 +17,7 @@ export function ModelConfigList(props: {
<>
<ListItem title={Locale.Settings.Model}>
<Select
aria-label={Locale.Settings.Model}
value={value}
onChange={(e) => {
const [model, providerName] = e.currentTarget.value.split("@");
@@ -40,6 +41,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.Temperature.SubTitle}
>
<InputRange
aria={Locale.Settings.Temperature.Title}
value={props.modelConfig.temperature?.toFixed(1)}
min="0"
max="1" // lets limit it to 0-1
@@ -59,6 +61,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.TopP.SubTitle}
>
<InputRange
aria={Locale.Settings.TopP.Title}
value={(props.modelConfig.top_p ?? 1).toFixed(1)}
min="0"
max="1"
@@ -78,6 +81,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.MaxTokens.SubTitle}
>
<input
aria-label={Locale.Settings.MaxTokens.Title}
type="number"
min={1024}
max={512000}
@@ -100,6 +104,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.PresencePenalty.SubTitle}
>
<InputRange
aria={Locale.Settings.PresencePenalty.Title}
value={props.modelConfig.presence_penalty?.toFixed(1)}
min="-2"
max="2"
@@ -121,6 +126,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.FrequencyPenalty.SubTitle}
>
<InputRange
aria={Locale.Settings.FrequencyPenalty.Title}
value={props.modelConfig.frequency_penalty?.toFixed(1)}
min="-2"
max="2"
@@ -142,6 +148,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.InjectSystemPrompts.SubTitle}
>
<input
aria-label={Locale.Settings.InjectSystemPrompts.Title}
type="checkbox"
checked={props.modelConfig.enableInjectSystemPrompts}
onChange={(e) =>
@@ -159,6 +166,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.InputTemplate.SubTitle}
>
<input
aria-label={Locale.Settings.InputTemplate.Title}
type="text"
value={props.modelConfig.template}
onChange={(e) =>
@@ -175,6 +183,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.HistoryCount.SubTitle}
>
<InputRange
aria={Locale.Settings.HistoryCount.Title}
title={props.modelConfig.historyMessageCount.toString()}
value={props.modelConfig.historyMessageCount}
min="0"
@@ -193,6 +202,7 @@ export function ModelConfigList(props: {
subTitle={Locale.Settings.CompressThreshold.SubTitle}
>
<input
aria-label={Locale.Settings.CompressThreshold.Title}
type="number"
min={500}
max={4000}
@@ -208,6 +218,7 @@ export function ModelConfigList(props: {
</ListItem>
<ListItem title={Locale.Memory.Title} subTitle={Locale.Memory.Send}>
<input
aria-label={Locale.Memory.Title}
type="checkbox"
checked={props.modelConfig.sendMemory}
onChange={(e) =>

View File

@@ -0,0 +1,16 @@
.plugin-title {
font-weight: bolder;
font-size: 16px;
margin: 10px 0;
}
.plugin-content {
font-size: 14px;
font-family: inherit;
pre code {
max-height: 240px;
overflow-y: auto;
white-space: pre-wrap;
min-width: 300px;
}
}

393
app/components/plugin.tsx Normal file
View File

@@ -0,0 +1,393 @@
import { useDebouncedCallback } from "use-debounce";
import OpenAPIClientAxios from "openapi-client-axios";
import yaml from "js-yaml";
import { PLUGINS_REPO_URL } from "../constant";
import { IconButton } from "./button";
import { ErrorBoundary } from "./error";
import styles from "./mask.module.scss";
import pluginStyles from "./plugin.module.scss";
import EditIcon from "../icons/edit.svg";
import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg";
import DeleteIcon from "../icons/delete.svg";
import EyeIcon from "../icons/eye.svg";
import ConfirmIcon from "../icons/confirm.svg";
import ReloadIcon from "../icons/reload.svg";
import GithubIcon from "../icons/github.svg";
import { Plugin, usePluginStore, FunctionToolService } from "../store/plugin";
import {
PasswordInput,
List,
ListItem,
Modal,
showConfirm,
showToast,
} from "./ui-lib";
import Locale from "../locales";
import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { getClientConfig } from "../config/client";
export function PluginPage() {
const navigate = useNavigate();
const pluginStore = usePluginStore();
const allPlugins = pluginStore.getAll();
const [searchPlugins, setSearchPlugins] = useState<Plugin[]>([]);
const [searchText, setSearchText] = useState("");
const plugins = searchText.length > 0 ? searchPlugins : allPlugins;
// refactored already, now it accurate
const onSearch = (text: string) => {
setSearchText(text);
if (text.length > 0) {
const result = allPlugins.filter(
(m) => m?.title.toLowerCase().includes(text.toLowerCase()),
);
setSearchPlugins(result);
} else {
setSearchPlugins(allPlugins);
}
};
const [editingPluginId, setEditingPluginId] = useState<string | undefined>();
const editingPlugin = pluginStore.get(editingPluginId);
const editingPluginTool = FunctionToolService.get(editingPlugin?.id);
const closePluginModal = () => setEditingPluginId(undefined);
const onChangePlugin = useDebouncedCallback((editingPlugin, e) => {
const content = e.target.innerText;
try {
const api = new OpenAPIClientAxios({
definition: yaml.load(content) as any,
});
api
.init()
.then(() => {
if (content != editingPlugin.content) {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.content = content;
const tool = FunctionToolService.add(plugin, true);
plugin.title = tool.api.definition.info.title;
plugin.version = tool.api.definition.info.version;
});
}
})
.catch((e) => {
console.error(e);
showToast(Locale.Plugin.EditModal.Error);
});
} catch (e) {
console.error(e);
showToast(Locale.Plugin.EditModal.Error);
}
}, 100).bind(null, editingPlugin);
const [loadUrl, setLoadUrl] = useState<string>("");
const loadFromUrl = (loadUrl: string) =>
fetch(loadUrl)
.catch((e) => {
const p = new URL(loadUrl);
return fetch(`/api/proxy/${p.pathname}?${p.search}`, {
headers: {
"X-Base-URL": p.origin,
},
});
})
.then((res) => res.text())
.then((content) => {
try {
return JSON.stringify(JSON.parse(content), null, " ");
} catch (e) {
return content;
}
})
.then((content) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.content = content;
const tool = FunctionToolService.add(plugin, true);
plugin.title = tool.api.definition.info.title;
plugin.version = tool.api.definition.info.version;
});
})
.catch((e) => {
showToast(Locale.Plugin.EditModal.Error);
});
return (
<ErrorBoundary>
<div className={styles["mask-page"]}>
<div className="window-header">
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.Plugin.Page.Title}
</div>
<div className="window-header-submai-title">
{Locale.Plugin.Page.SubTitle(plugins.length)}
</div>
</div>
<div className="window-actions">
<div className="window-action-button">
<a
href={PLUGINS_REPO_URL}
target="_blank"
rel="noopener noreferrer"
>
<IconButton icon={<GithubIcon />} bordered />
</a>
</div>
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
bordered
onClick={() => navigate(-1)}
/>
</div>
</div>
</div>
<div className={styles["mask-page-body"]}>
<div className={styles["mask-filter"]}>
<input
type="text"
className={styles["search-bar"]}
placeholder={Locale.Plugin.Page.Search}
autoFocus
onInput={(e) => onSearch(e.currentTarget.value)}
/>
<IconButton
className={styles["mask-create"]}
icon={<AddIcon />}
text={Locale.Plugin.Page.Create}
bordered
onClick={() => {
const createdPlugin = pluginStore.create();
setEditingPluginId(createdPlugin.id);
}}
/>
</div>
<div>
{plugins.length == 0 && (
<div
style={{
display: "flex",
margin: "60px auto",
alignItems: "center",
justifyContent: "center",
}}
>
{Locale.Plugin.Page.Find}
<a
href={PLUGINS_REPO_URL}
target="_blank"
rel="noopener noreferrer"
style={{ marginLeft: 16 }}
>
<IconButton icon={<GithubIcon />} bordered />
</a>
</div>
)}
{plugins.map((m) => (
<div className={styles["mask-item"]} key={m.id}>
<div className={styles["mask-header"]}>
<div className={styles["mask-icon"]}></div>
<div className={styles["mask-title"]}>
<div className={styles["mask-name"]}>
{m.title}@<small>{m.version}</small>
</div>
<div className={styles["mask-info"] + " one-line"}>
{Locale.Plugin.Item.Info(
FunctionToolService.add(m).length,
)}
</div>
</div>
</div>
<div className={styles["mask-actions"]}>
{m.builtin ? (
<IconButton
icon={<EyeIcon />}
text={Locale.Plugin.Item.View}
onClick={() => setEditingPluginId(m.id)}
/>
) : (
<IconButton
icon={<EditIcon />}
text={Locale.Plugin.Item.Edit}
onClick={() => setEditingPluginId(m.id)}
/>
)}
{!m.builtin && (
<IconButton
icon={<DeleteIcon />}
text={Locale.Plugin.Item.Delete}
onClick={async () => {
if (
await showConfirm(Locale.Plugin.Item.DeleteConfirm)
) {
pluginStore.delete(m.id);
}
}}
/>
)}
</div>
</div>
))}
</div>
</div>
</div>
{editingPlugin && (
<div className="modal-mask">
<Modal
title={Locale.Plugin.EditModal.Title(editingPlugin?.builtin)}
onClose={closePluginModal}
actions={[
<IconButton
icon={<ConfirmIcon />}
text={Locale.UI.Confirm}
key="export"
bordered
onClick={() => setEditingPluginId("")}
/>,
]}
>
<List>
<ListItem title={Locale.Plugin.EditModal.Auth}>
<select
value={editingPlugin?.authType}
onChange={(e) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.authType = e.target.value;
});
}}
>
<option value="">{Locale.Plugin.Auth.None}</option>
<option value="bearer">{Locale.Plugin.Auth.Bearer}</option>
<option value="basic">{Locale.Plugin.Auth.Basic}</option>
<option value="custom">{Locale.Plugin.Auth.Custom}</option>
</select>
</ListItem>
{["bearer", "basic", "custom"].includes(
editingPlugin.authType as string,
) && (
<ListItem title={Locale.Plugin.Auth.Location}>
<select
value={editingPlugin?.authLocation}
onChange={(e) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.authLocation = e.target.value;
});
}}
>
<option value="header">
{Locale.Plugin.Auth.LocationHeader}
</option>
<option value="query">
{Locale.Plugin.Auth.LocationQuery}
</option>
<option value="body">
{Locale.Plugin.Auth.LocationBody}
</option>
</select>
</ListItem>
)}
{editingPlugin.authType == "custom" && (
<ListItem title={Locale.Plugin.Auth.CustomHeader}>
<input
type="text"
value={editingPlugin?.authHeader}
onChange={(e) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.authHeader = e.target.value;
});
}}
></input>
</ListItem>
)}
{["bearer", "basic", "custom"].includes(
editingPlugin.authType as string,
) && (
<ListItem title={Locale.Plugin.Auth.Token}>
<PasswordInput
type="text"
value={editingPlugin?.authToken}
onChange={(e) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.authToken = e.currentTarget.value;
});
}}
></PasswordInput>
</ListItem>
)}
{!getClientConfig()?.isApp && (
<ListItem
title={Locale.Plugin.Auth.Proxy}
subTitle={Locale.Plugin.Auth.ProxyDescription}
>
<input
type="checkbox"
checked={editingPlugin?.usingProxy}
style={{ minWidth: 16 }}
onChange={(e) => {
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
plugin.usingProxy = e.currentTarget.checked;
});
}}
></input>
</ListItem>
)}
</List>
<List>
<ListItem title={Locale.Plugin.EditModal.Content}>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<input
type="text"
style={{ minWidth: 200, marginRight: 20 }}
onInput={(e) => setLoadUrl(e.currentTarget.value)}
></input>
<IconButton
icon={<ReloadIcon />}
text={Locale.Plugin.EditModal.Load}
bordered
onClick={() => loadFromUrl(loadUrl)}
/>
</div>
</ListItem>
<ListItem
subTitle={
<div
className={`markdown-body ${pluginStyles["plugin-content"]}`}
dir="auto"
>
<pre>
<code
contentEditable={true}
dangerouslySetInnerHTML={{
__html: editingPlugin.content,
}}
onBlur={onChangePlugin}
></code>
</pre>
</div>
}
></ListItem>
{editingPluginTool?.tools.map((tool, index) => (
<ListItem
key={index}
title={tool?.function?.name}
subTitle={tool?.function?.description}
/>
))}
</List>
</Modal>
</div>
)}
</ErrorBoundary>
);
}

View File

@@ -0,0 +1,2 @@
export * from "./sd";
export * from "./sd-panel";

View File

@@ -0,0 +1,45 @@
.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%;
}
}

View File

@@ -0,0 +1,320 @@
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>
</>
);
}

View File

@@ -0,0 +1,140 @@
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>
);
}

View File

@@ -0,0 +1,53 @@
.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%;
}
}
}

336
app/components/sd/sd.tsx Normal file
View File

@@ -0,0 +1,336 @@
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>
</>
);
}

View File

@@ -0,0 +1,167 @@
import { useState, useEffect, useRef, useCallback } from "react";
import { ErrorBoundary } from "./error";
import styles from "./mask.module.scss";
import { useNavigate } from "react-router-dom";
import { IconButton } from "./button";
import CloseIcon from "../icons/close.svg";
import EyeIcon from "../icons/eye.svg";
import Locale from "../locales";
import { Path } from "../constant";
import { useChatStore } from "../store";
type Item = {
id: number;
name: string;
content: string;
};
export function SearchChatPage() {
const navigate = useNavigate();
const chatStore = useChatStore();
const sessions = chatStore.sessions;
const selectSession = chatStore.selectSession;
const [searchResults, setSearchResults] = useState<Item[]>([]);
const previousValueRef = useRef<string>("");
const searchInputRef = useRef<HTMLInputElement>(null);
const doSearch = useCallback((text: string) => {
const lowerCaseText = text.toLowerCase();
const results: Item[] = [];
sessions.forEach((session, index) => {
const fullTextContents: string[] = [];
session.messages.forEach((message) => {
const content = message.content as string;
if (!content.toLowerCase || content === "") return;
const lowerCaseContent = content.toLowerCase();
// full text search
let pos = lowerCaseContent.indexOf(lowerCaseText);
while (pos !== -1) {
const start = Math.max(0, pos - 35);
const end = Math.min(content.length, pos + lowerCaseText.length + 35);
fullTextContents.push(content.substring(start, end));
pos = lowerCaseContent.indexOf(
lowerCaseText,
pos + lowerCaseText.length,
);
}
});
if (fullTextContents.length > 0) {
results.push({
id: index,
name: session.topic,
content: fullTextContents.join("... "), // concat content with...
});
}
});
// sort by length of matching content
results.sort((a, b) => b.content.length - a.content.length);
return results;
}, []);
useEffect(() => {
const intervalId = setInterval(() => {
if (searchInputRef.current) {
const currentValue = searchInputRef.current.value;
if (currentValue !== previousValueRef.current) {
if (currentValue.length > 0) {
const result = doSearch(currentValue);
setSearchResults(result);
}
previousValueRef.current = currentValue;
}
}
}, 1000);
// Cleanup the interval on component unmount
return () => clearInterval(intervalId);
}, [doSearch]);
return (
<ErrorBoundary>
<div className={styles["mask-page"]}>
{/* header */}
<div className="window-header">
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.SearchChat.Page.Title}
</div>
<div className="window-header-submai-title">
{Locale.SearchChat.Page.SubTitle(searchResults.length)}
</div>
</div>
<div className="window-actions">
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
bordered
onClick={() => navigate(-1)}
/>
</div>
</div>
</div>
<div className={styles["mask-page-body"]}>
<div className={styles["mask-filter"]}>
{/**搜索输入框 */}
<input
type="text"
className={styles["search-bar"]}
placeholder={Locale.SearchChat.Page.Search}
autoFocus
ref={searchInputRef}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const searchText = e.currentTarget.value;
if (searchText.length > 0) {
const result = doSearch(searchText);
setSearchResults(result);
}
}
}}
/>
</div>
<div>
{searchResults.map((item) => (
<div
className={styles["mask-item"]}
key={item.id}
onClick={() => {
navigate(Path.Chat);
selectSession(item.id);
}}
style={{ cursor: "pointer" }}
>
{/** 搜索匹配的文本 */}
<div className={styles["mask-header"]}>
<div className={styles["mask-title"]}>
<div className={styles["mask-name"]}>{item.name}</div>
{item.content.slice(0, 70)}
</div>
</div>
{/** 操作按钮 */}
<div className={styles["mask-actions"]}>
<IconButton
icon={<EyeIcon />}
text={Locale.SearchChat.Item.View}
/>
</div>
</div>
))}
</div>
</div>
</div>
</ErrorBoundary>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useMemo } from "react";
import React, { useEffect, useRef, useMemo, useState, Fragment } from "react";
import styles from "./home.module.scss";
@@ -10,8 +10,8 @@ import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg";
import DeleteIcon from "../icons/delete.svg";
import MaskIcon from "../icons/mask.svg";
import PluginIcon from "../icons/plugin.svg";
import DragIcon from "../icons/drag.svg";
import DiscoveryIcon from "../icons/discovery.svg";
import Locale from "../locales";
@@ -23,19 +23,20 @@ import {
MIN_SIDEBAR_WIDTH,
NARROW_SIDEBAR_WIDTH,
Path,
PLUGINS,
REPO_URL,
} from "../constant";
import { Link, useNavigate } from "react-router-dom";
import { isIOS, useMobileScreen } from "../utils";
import dynamic from "next/dynamic";
import { showConfirm, showToast } from "./ui-lib";
import { showConfirm, Selector } from "./ui-lib";
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
loading: () => null,
});
function useHotKey() {
export function useHotKey() {
const chatStore = useChatStore();
useEffect(() => {
@@ -54,7 +55,7 @@ function useHotKey() {
});
}
function useDragSideBar() {
export function useDragSideBar() {
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
const config = useAppConfig();
@@ -127,25 +128,21 @@ function useDragSideBar() {
shouldNarrow,
};
}
export function SideBar(props: { className?: string }) {
const chatStore = useChatStore();
// drag side bar
const { onDragStart, shouldNarrow } = useDragSideBar();
const navigate = useNavigate();
const config = useAppConfig();
export function SideBarContainer(props: {
children: React.ReactNode;
onDragStart: (e: MouseEvent) => void;
shouldNarrow: boolean;
className?: string;
}) {
const isMobileScreen = useMobileScreen();
const isIOSMobile = useMemo(
() => isIOS() && isMobileScreen,
[isMobileScreen],
);
useHotKey();
const { children, className, onDragStart, shouldNarrow } = props;
return (
<div
className={`${styles.sidebar} ${props.className} ${
className={`${styles.sidebar} ${className} ${
shouldNarrow && styles["narrow-sidebar"]
}`}
style={{
@@ -153,43 +150,130 @@ export function SideBar(props: { className?: string }) {
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
}}
>
<div className={styles["sidebar-header"]} data-tauri-drag-region>
<div className={styles["sidebar-title"]} data-tauri-drag-region>
NextChat
</div>
<div className={styles["sidebar-sub-title"]}>
Build your own AI assistant.
</div>
<div className={styles["sidebar-logo"] + " no-dark"}>
<ChatGptIcon />
</div>
</div>
<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={<PluginIcon />}
text={shouldNarrow ? undefined : Locale.Plugin.Name}
className={styles["sidebar-bar-button"]}
onClick={() => showToast(Locale.WIP)}
shadow
/>
</div>
{children}
<div
className={styles["sidebar-body"]}
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-title-container"]}>
<div className={styles["sidebar-title"]} data-tauri-drag-region>
{title}
</div>
<div className={styles["sidebar-sub-title"]}>{subTitle}</div>
</div>
<div className={styles["sidebar-logo"] + " no-dark"}>{logo}</div>
</div>
{children}
</Fragment>
);
}
export function SideBarBody(props: {
children: React.ReactNode;
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}) {
const { onClick, children } = props;
return (
<div className={styles["sidebar-body"]} onClick={onClick}>
{children}
</div>
);
}
export function SideBarTail(props: {
primaryAction?: React.ReactNode;
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) => {
if (e.target === e.currentTarget) {
navigate(Path.Home);
@@ -197,32 +281,41 @@ export function SideBar(props: { className?: string }) {
}}
>
<ChatList narrow={shouldNarrow} />
</div>
<div className={styles["sidebar-tail"]}>
<div className={styles["sidebar-actions"]}>
<div className={styles["sidebar-action"] + " " + styles.mobile}>
<IconButton
icon={<DeleteIcon />}
onClick={async () => {
if (await showConfirm(Locale.Home.DeleteChat)) {
chatStore.deleteSession(chatStore.currentSessionIndex);
}
}}
/>
</div>
<div className={styles["sidebar-action"]}>
<Link to={Path.Settings}>
<IconButton icon={<SettingsIcon />} shadow />
</Link>
</div>
<div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
<IconButton icon={<GithubIcon />} shadow />
</a>
</div>
</div>
<div>
</SideBarBody>
<SideBarTail
primaryAction={
<>
<div className={styles["sidebar-action"] + " " + styles.mobile}>
<IconButton
icon={<DeleteIcon />}
onClick={async () => {
if (await showConfirm(Locale.Home.DeleteChat)) {
chatStore.deleteSession(chatStore.currentSessionIndex);
}
}}
/>
</div>
<div className={styles["sidebar-action"]}>
<Link to={Path.Settings}>
<IconButton
aria={Locale.Settings.Title}
icon={<SettingsIcon />}
shadow
/>
</Link>
</div>
<div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
<IconButton
aria={Locale.Export.MessageFromChatGPT}
icon={<GithubIcon />}
shadow
/>
</a>
</div>
</>
}
secondaryAction={
<IconButton
icon={<AddIcon />}
text={shouldNarrow ? undefined : Locale.Home.NewChat}
@@ -236,15 +329,8 @@ export function SideBar(props: { className?: string }) {
}}
shadow
/>
</div>
</div>
<div
className={styles["sidebar-drag"]}
onPointerDown={(e) => onDragStart(e as any)}
>
<DragIcon />
</div>
</div>
}
/>
</SideBarContainer>
);
}

View File

@@ -61,6 +61,19 @@
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 {
@@ -291,7 +304,12 @@
justify-content: center;
z-index: 999;
.selector-item-disabled{
opacity: 0.6;
}
&-content {
min-width: 300px;
.list {
max-height: 90vh;
overflow-x: hidden;

View File

@@ -13,7 +13,15 @@ import MinIcon from "../icons/min.svg";
import Locale from "../locales";
import { createRoot } from "react-dom/client";
import React, { HTMLProps, useEffect, useState } from "react";
import React, {
CSSProperties,
HTMLProps,
MouseEvent,
useEffect,
useState,
useCallback,
useRef,
} from "react";
import { IconButton } from "./button";
export function Popover(props: {
@@ -42,16 +50,21 @@ export function Card(props: { children: JSX.Element[]; className?: string }) {
}
export function ListItem(props: {
title: string;
subTitle?: string;
title?: string;
subTitle?: string | JSX.Element;
children?: JSX.Element | JSX.Element[];
icon?: JSX.Element;
className?: string;
onClick?: () => void;
onClick?: (e: MouseEvent) => void;
vertical?: boolean;
}) {
return (
<div
className={styles["list-item"] + ` ${props.className || ""}`}
className={
styles["list-item"] +
` ${props.vertical ? styles["vertical"] : ""} ` +
` ${props.className || ""}`
}
onClick={props.onClick}
>
<div className={styles["list-header"]}>
@@ -252,9 +265,10 @@ export function Input(props: InputProps) {
);
}
export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
export function PasswordInput(
props: HTMLProps<HTMLInputElement> & { aria?: string },
) {
const [visible, setVisible] = useState(false);
function changeVisibility() {
setVisible(!visible);
}
@@ -262,6 +276,7 @@ export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
return (
<div className={"password-input-container"}>
<IconButton
aria={props.aria}
icon={visible ? <EyeIcon /> : <EyeOffIcon />}
onClick={changeVisibility}
className={"password-eye"}
@@ -420,17 +435,25 @@ export function showPrompt(content: any, value = "", rows = 3) {
});
}
export function showImageModal(img: string) {
export function showImageModal(
img: string,
defaultMax?: boolean,
style?: CSSProperties,
boxStyle?: CSSProperties,
) {
showModal({
title: Locale.Export.Image.Modal,
defaultMax: defaultMax,
children: (
<div>
<div style={{ display: "flex", justifyContent: "center", ...boxStyle }}>
<img
src={img}
alt="preview"
style={{
maxWidth: "100%",
}}
style={
style ?? {
maxWidth: "100%",
}
}
></img>
</div>
),
@@ -442,27 +465,56 @@ export function Selector<T>(props: {
title: string;
subTitle?: string;
value: T;
disable?: boolean;
}>;
defaultSelectedValue?: T;
defaultSelectedValue?: T[] | T;
onSelection?: (selection: T[]) => void;
onClose?: () => void;
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 (
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
<div className={styles["selector-content"]}>
<List>
{props.items.map((item, i) => {
const selected = props.defaultSelectedValue === item.value;
const selected = selectedValues.includes(item.value);
return (
<ListItem
className={styles["selector-item"]}
className={`${styles["selector-item"]} ${
item.disable && styles["selector-item-disabled"]
}`}
key={i}
title={item.title}
subTitle={item.subTitle}
onClick={() => {
props.onSelection?.([item.value]);
props.onClose?.();
onClick={(e) => {
if (item.disable) {
e.stopPropagation();
} else {
handleSelection(e, item.value);
}
}}
>
{selected ? (
@@ -485,3 +537,38 @@ export function Selector<T>(props: {
</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>
);
}

View File

@@ -3,7 +3,7 @@ import { BuildConfig, getBuildConfig } from "./build";
export function getClientConfig() {
if (typeof document !== "undefined") {
// client side
return JSON.parse(queryMeta("config")) as BuildConfig;
return JSON.parse(queryMeta("config") || "{}") as BuildConfig;
}
if (typeof process !== "undefined") {

View File

@@ -1,5 +1,5 @@
import md5 from "spark-md5";
import { DEFAULT_MODELS } from "../constant";
import { DEFAULT_MODELS, DEFAULT_GA_ID } from "../constant";
declare global {
namespace NodeJS {
@@ -21,7 +21,11 @@ declare global {
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
CUSTOM_MODELS?: string; // to control custom models
DEFAULT_MODEL?: string; // to cnntrol default model in every new chat window
DEFAULT_MODEL?: string; // to control default model in every new chat window
// stability only
STABILITY_URL?: string;
STABILITY_API_KEY?: string;
// azure only
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
@@ -53,6 +57,20 @@ declare global {
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;
}
@@ -101,19 +119,30 @@ export const getServerSideConfig = () => {
if (disableGPT4) {
if (customModels) customModels += ",";
customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4"))
customModels += DEFAULT_MODELS.filter(
(m) => m.name.startsWith("gpt-4") && !m.name.startsWith("gpt-4o-mini"),
)
.map((m) => "-" + m.name)
.join(",");
if (defaultModel.startsWith("gpt-4")) defaultModel = "";
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 isGoogle = !!process.env.GOOGLE_API_KEY;
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
const isTencent = !!process.env.TENCENT_API_KEY;
const isBaidu = !!process.env.BAIDU_API_KEY;
const isBytedance = !!process.env.BYTEDANCE_API_KEY;
const isAlibaba = !!process.env.ALIBABA_API_KEY;
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
const isIflytek = !!process.env.IFLYTEK_API_KEY;
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
@@ -131,6 +160,10 @@ export const getServerSideConfig = () => {
apiKey: getApiKey(process.env.OPENAI_API_KEY),
openaiOrgId: process.env.OPENAI_ORG_ID,
isStability,
stabilityUrl: process.env.STABILITY_URL,
stabilityApiKey: getApiKey(process.env.STABILITY_API_KEY),
isAzure,
azureUrl: process.env.AZURE_URL,
azureApiKey: getApiKey(process.env.AZURE_API_KEY),
@@ -158,7 +191,27 @@ export const getServerSideConfig = () => {
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,
gaId: process.env.GA_ID || DEFAULT_GA_ID,
needCode: ACCESS_CODES.size > 0,
code: process.env.CODE,

View File

@@ -1,6 +1,9 @@
export const OWNER = "Yidadaa";
import path from "path";
export const OWNER = "ChatGPTNextWeb";
export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
export const PLUGINS_REPO_URL = `https://github.com/${OWNER}/NextChat-Awesome-Plugins`;
export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
export const UPDATE_URL = `${REPO_URL}#keep-updated`;
export const RELEASE_URL = `${REPO_URL}/releases`;
@@ -8,6 +11,8 @@ 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 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 OPENAI_BASE_URL = "https://api.openai.com";
export const ANTHROPIC_BASE_URL = "https://api.anthropic.com";
@@ -21,13 +26,26 @@ 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 {
Home = "/",
Chat = "/chat",
Settings = "/settings",
NewChat = "/new-chat",
Masks = "/masks",
Plugins = "/plugins",
Auth = "/auth",
Sd = "/sd",
SdNew = "/sd-new",
Artifacts = "/artifacts",
SearchChat = "/search-chat",
}
export enum ApiPath {
@@ -39,6 +57,11 @@ export enum ApiPath {
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 {
@@ -53,12 +76,14 @@ export enum FileName {
export enum StoreKey {
Chat = "chat-next-web-store",
Plugin = "chat-next-web-plugin",
Access = "access-control",
Config = "app-config",
Mask = "mask-store",
Prompt = "prompt-store",
Update = "chat-update",
Sync = "sync",
SdList = "sd-list",
}
export const DEFAULT_SIDEBAR_WIDTH = 300;
@@ -85,17 +110,39 @@ export enum ServiceProvider {
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 {
Stability = "Stability",
GPT = "GPT",
GeminiPro = "GeminiPro",
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 = {
ChatPath: "v1/messages",
ChatPath1: "v1/complete",
@@ -105,6 +152,7 @@ export const Anthropic = {
export const OpenaiPath = {
ChatPath: "v1/chat/completions",
ImagePath: "v1/images/generations",
UsagePath: "dashboard/billing/usage",
SubsPath: "dashboard/billing/subscription",
ListModelPath: "v1/models",
@@ -113,12 +161,16 @@ export const OpenaiPath = {
export const Azure = {
ChatPath: (deployName: string, apiVersion: string) =>
`deployments/${deployName}/chat/completions?api-version=${apiVersion}`,
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
// 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 = {
ExampleEndpoint: "https://generativelanguage.googleapis.com/",
ChatPath: (modelName: string) => `v1beta/models/${modelName}:generateContent`,
ChatPath: (modelName: string) =>
`v1beta/models/${modelName}:streamGenerateContent`,
};
export const Baidu = {
@@ -134,6 +186,9 @@ export const Baidu = {
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}`;
},
};
@@ -148,6 +203,20 @@ export const Alibaba = {
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_SYSTEM_TEMPLATE = `
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
@@ -166,7 +235,7 @@ Latex inline: \\(x^2\\)
Latex block: $$e=mc^2$$
`;
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
export const SUMMARIZE_MODEL = "gpt-4o-mini";
export const GEMINI_SUMMARIZE_MODEL = "gemini-pro";
export const KnowledgeCutOffDate: Record<string, string> = {
@@ -176,6 +245,9 @@ export const KnowledgeCutOffDate: Record<string, string> = {
"gpt-4-turbo-preview": "2023-12",
"gpt-4o": "2023-10",
"gpt-4o-2024-05-13": "2023-10",
"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",
// After improvements,
// it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously.
@@ -195,9 +267,13 @@ const openaiModels = [
"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 = [
@@ -225,6 +301,10 @@ const baiduModels = [
"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 = [
@@ -246,68 +326,136 @@ const alibabaModes = [
"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 = [
...openaiModels.map((name) => ({
name,
available: true,
sorted: seq++, // Global sequence sort(index)
provider: {
id: "openai",
providerName: "OpenAI",
providerType: "openai",
sorted: 1, // 这里是固定的,确保顺序与之前内置的版本一致
},
})),
...openaiModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "azure",
providerName: "Azure",
providerType: "azure",
sorted: 2,
},
})),
...googleModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "google",
providerName: "Google",
providerType: "google",
sorted: 3,
},
})),
...anthropicModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "anthropic",
providerName: "Anthropic",
providerType: "anthropic",
sorted: 4,
},
})),
...baiduModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "baidu",
providerName: "Baidu",
providerType: "baidu",
sorted: 5,
},
})),
...bytedanceModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "bytedance",
providerName: "ByteDance",
providerType: "bytedance",
sorted: 6,
},
})),
...alibabaModes.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "alibaba",
providerName: "Alibaba",
providerType: "alibaba",
sorted: 7,
},
})),
...tencentModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "tencent",
providerName: "Tencent",
providerType: "tencent",
sorted: 8,
},
})),
...moonshotModes.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "moonshot",
providerName: "Moonshot",
providerType: "moonshot",
sorted: 9,
},
})),
...iflytekModels.map((name) => ({
name,
available: true,
sorted: seq++,
provider: {
id: "iflytek",
providerName: "Iflytek",
providerType: "iflytek",
sorted: 10,
},
})),
] as const;
@@ -327,3 +475,10 @@ export const internalAllowedWebDavEndpoints = [
"https://webdav.yandex.com",
"https://app.koofr.net/dav/Koofr",
];
export const DEFAULT_GA_ID = "G-89WN60ZK2E";
export const PLUGINS = [
{ name: "Plugins", path: Path.Plugins },
{ name: "Stable Diffusion", path: Path.Sd },
{ name: "Search Chat", path: Path.SearchChat },
];

8
app/global.d.ts vendored
View File

@@ -21,10 +21,16 @@ declare interface Window {
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
writeTextFile(path: string, data: string): Promise<void>;
};
notification:{
notification: {
requestPermission(): Promise<Permission>;
isPermissionGranted(): Promise<boolean>;
sendNotification(options: string | Options): void;
};
http: {
fetch<T>(
url: string,
options?: Record<string, unknown>,
): Promise<Response<T>>;
};
};
}

7
app/icons/discovery.svg Normal file
View File

@@ -0,0 +1,7 @@
<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>

After

Width:  |  Height:  |  Size: 371 B

4
app/icons/hd.svg Normal file
View File

@@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 514 B

10
app/icons/history.svg Normal file
View File

@@ -0,0 +1,10 @@
<?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>

After

Width:  |  Height:  |  Size: 660 B

4
app/icons/palette.svg Normal file
View File

@@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 781 B

12
app/icons/sd.svg Normal file
View File

@@ -0,0 +1,12 @@
<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>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
app/icons/size.svg Normal file
View File

@@ -0,0 +1 @@
<?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>

After

Width:  |  Height:  |  Size: 681 B

1
app/icons/zoom.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1.2rem" height="1.2rem" viewBox="0 0 24 24"><g fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></g></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -6,7 +6,7 @@ import { getClientConfig } from "./config/client";
import type { Metadata, Viewport } from "next";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { getServerSideConfig } from "./config/server";
import { GoogleTagManager } from "@next/third-parties/google";
import { GoogleTagManager, GoogleAnalytics } from "@next/third-parties/google";
const serverConfig = getServerSideConfig();
export const metadata: Metadata = {
@@ -37,8 +37,11 @@ export default function RootLayout({
<html lang="en">
<head>
<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>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link rel="manifest" href="/site.webmanifest" crossOrigin="use-credentials"></link>
<script src="/serviceWorkerRegister.js" defer></script>
</head>
<body>
@@ -53,6 +56,11 @@ export default function RootLayout({
<GoogleTagManager gtmId={serverConfig.gtmId} />
</>
)}
{serverConfig?.gaId && (
<>
<GoogleAnalytics gaId={serverConfig.gaId} />
</>
)}
</body>
</html>
);

View File

@@ -1,274 +1,535 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const ar: PartialLocaleType = {
WIP: "قريبًا...",
Error: {
Unauthorized:
"غير مصرح بالوصول، يرجى إدخال رمز الوصول [auth](/#/auth) في صفحة المصادقة.",
Unauthorized: isApp
? "تم اكتشاف مفتاح API غير صالح، يرجى الذهاب إلى [الإعدادات](/#/settings) للتحقق من صحة مفتاح API."
: "كلمة المرور غير صحيحة أو فارغة، يرجى الذهاب إلى [تسجيل الدخول](/#/auth) لإدخال كلمة مرور صحيحة، أو أدخل مفتاح OpenAI API الخاص بك في [الإعدادات](/#/settings).",
},
Auth: {
Title: "تحتاج إلى رمز الوصول",
Tips: "يرجى إدخال رمز الوصول أدناه",
SubTips: "أو أدخل مفتاح واجهة برمجة تطبيقات OpenAI الخاص بك",
Input: "رمز الوصول",
Title: "تحتاج إلى كلمة مرور",
Tips: "قام المشرف بتفعيل التحقق بكلمة المرور، يرجى إدخال رمز الوصول أدناه",
SubTips: "أو إدخال مفتاح API الخاص بـ OpenAI أو Google",
Input: "أدخل رمز الوصول هنا",
Confirm: "تأكيد",
Later: "لاحقًا",
Later: "في وقت لاحق",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} رسائل`,
ChatItemCount: (count: number) => `${count} محادثة`,
},
Chat: {
SubTitle: (count: number) => ` ${count} رسائل مع ChatGPT`,
SubTitle: (count: number) => `إجمالي ${count} محادثة`,
EditMessage: {
Title: "تحرير سجل الرسائل",
Topic: {
Title: "موضوع الدردشة",
SubTitle: "تغيير موضوع الدردشة الحالي",
},
},
Actions: {
ChatList: "الانتقال إلى قائمة الدردشة",
CompressedHistory: "ملخص ضغط ذاكرة التاريخ",
Export: "تصدير جميع الرسائل كـ Markdown",
ChatList: "عرض قائمة الرسائل",
CompressedHistory: "عرض التاريخ المضغوط",
Export: "تصدير سجل الدردشة",
Copy: "نسخ",
Stop: "توقف",
Stop: "إيقاف",
Retry: "إعادة المحاولة",
Pin: "تثبيت",
PinToastContent: "تم تثبيت 1 محادثة في الإشعارات المسبقة",
PinToastAction: "عرض",
Delete: "حذف",
Edit: "تحرير",
},
Commands: {
new: "دردشة جديدة",
newm: "إنشاء دردشة من القناع",
next: "الدردشة التالية",
prev: "الدردشة السابقة",
clear: "مسح السياق",
del: "حذف الدردشة",
},
InputActions: {
Stop: "توقف",
ToBottom: "إلى آخر",
Stop: "إيقاف الاستجابة",
ToBottom: "الانتقال إلى الأحدث",
Theme: {
auto: "تلقائي",
light: "نمط فاتح",
dark: "نمط داكن",
auto: "موضوع تلقائي",
light: "الوضع الفاتح",
dark: "الوضع الداكن",
},
Prompt: "الاقتراحات",
Masks: "الأقنعة",
Clear: "مسح السياق",
Settings: "الإعدادات",
Prompt: "الأوامر السريعة",
Masks: "جميع الأقنعة",
Clear: "مسح الدردشة",
Settings: "إعدادات الدردشة",
UploadImage: "تحميل صورة",
},
Rename: "إعادة تسمية الدردشة",
Typing: "كتابة...",
Typing: "يكتب…",
Input: (submitKey: string) => {
var inputHints = ` اضغط على ${submitKey} للإرسال`;
var inputHints = `${submitKey} إرسال`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += "، Shift + Enter للإنشاء";
inputHints += "، Shift + Enter لإدراج سطر جديد";
}
return inputHints + "، / للبحث في الاقتراحات";
return inputHints + "، / لتفعيل الإكمال التلقائي، : لتفعيل الأوامر";
},
Send: "إرسال",
Config: {
Reset: "إعادة التعيين إلى الإعدادات الافتراضية",
SaveAs: "حفظ كأقنعة",
Reset: "مسح الذاكرة",
SaveAs: "حفظ كقناع",
},
IsContext: "الإشعارات المسبقة",
},
Export: {
Title: "تصدير الرسائل",
Title: "مشاركة سجل الدردشة",
Copy: "نسخ الكل",
Download: نزيل",
MessageFromYou: "رسالة منك",
MessageFromChatGPT: "رسالة من ChatGPT",
Download: حميل الملف",
Share: "مشاركة على ShareGPT",
MessageFromYou: "المستخدم",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "صيغة التصدير",
SubTitle: "Markdown أو صورة PNG",
Title: "تنسيق التصدير",
SubTitle: "يمكنك تصدير النص كـ Markdown أو صورة PNG",
},
IncludeContext: {
Title: "تضمين السياق",
SubTitle: "تصدير اقتراحات السياق في الأقنعة أم لا",
Title: "تضمين سياق القناع",
SubTitle: "هل تريد عرض سياق القناع في الرسائل",
},
Steps: {
Select: "تحديد",
Select: "اختيار",
Preview: "معاينة",
},
Image: {
Toast: "يتم إنشاء لقطة الشاشة",
Modal: "اضغط مطولاً أو انقر بزر الماوس الأيمن لحفظ الصورة",
},
},
Select: {
Search: "بحث",
Search: "بحث في الرسائل",
All: "تحديد الكل",
Latest: "تحديد أحدث",
Clear: "مسح",
Latest: "أحدث الرسائل",
Clear: "مسح التحديد",
},
Memory: {
Title: "اقتراحات الذاكرة",
EmptyContent: "لا شيء حتى الآن.",
Send: "إرسال الذاكرة",
Copy: "نسخ الذاكرة",
Reset: "إعادة التعيين",
ResetConfirm:
"سيؤدي إعادة التعيين إلى مسح سجل المحادثة الحالي والذاكرة التاريخية. هل أنت متأكد أنك تريد الاستمرار؟",
Title: "ملخص التاريخ",
EmptyContent: "محتوى المحادثة قصير جداً، لا حاجة للتلخيص",
Send: "ضغط تلقائي لسجل الدردشة كـ سياق",
Copy: "نسخ الملخص",
Reset: "[غير مستخدم]",
ResetConfirm: "تأكيد مسح ملخص التاريخ؟",
},
Home: {
NewChat: "دردشة جديدة",
DeleteChat: "هل تريد تأكيد حذف المحادثة المحددة؟",
DeleteToast: "تم حذف الدردشة",
Revert: "التراجع",
DeleteChat: "تأكيد حذف المحادثة المحددة؟",
DeleteToast: "تم حذف المحادثة",
Revert: "تراجع",
},
Settings: {
Title: "الإعدادات",
SubTitle: "جميع الإعدادات",
SubTitle: "جميع خيارات الإعدادات",
Lang: {
Name: "Language", // تنبيه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة وتركها "Language"
All: "كل اللغات",
Danger: {
Reset: {
Title: "إعادة تعيين جميع الإعدادات",
SubTitle: "إعادة تعيين جميع عناصر الإعدادات إلى القيم الافتراضية",
Action: "إعادة التعيين الآن",
Confirm: "تأكيد إعادة تعيين جميع الإعدادات؟",
},
Clear: {
Title: "مسح جميع البيانات",
SubTitle: "مسح جميع الدردشات وبيانات الإعدادات",
Action: "مسح الآن",
Confirm: "تأكيد مسح جميع الدردشات وبيانات الإعدادات؟",
},
},
Avatar: "الصورة الرمزية",
Lang: {
Name: "Language", // انتبه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة، اتركها كما هي "Language"
All: "جميع اللغات",
},
Avatar: "الصورة الشخصية",
FontSize: {
Title: "حجم الخط",
SubTitle: "ضبط حجم الخط لمحتوى الدردشة",
SubTitle: "حجم الخط في محتوى الدردشة",
},
FontFamily: {
Title: "خط الدردشة",
SubTitle: "خط محتوى الدردشة، اتركه فارغًا لتطبيق الخط الافتراضي العالمي",
Placeholder: "اسم الخط",
},
InjectSystemPrompts: {
Title: "حقن تلميحات النظام",
Title: "حقن الرسائل النصية النظامية",
SubTitle:
"قم بإضافة تلميحة نظام محاكاة ChatGPT إلى بداية قائمة الرسائل المُطلَبة في كل طلب",
"فرض إضافة رسالة نظامية تحاكي ChatGPT في بداية قائمة الرسائل لكل طلب",
},
InputTemplate: {
Title: "نموذج الإدخال",
SubTitle: "سيتم ملء أحدث رسالة في هذا النموذج",
Title: "معالجة الإدخال من قبل المستخدم",
SubTitle: "سيتم ملء آخر رسالة من المستخدم في هذا القالب",
},
Update: {
Version: (x: string) => ` الإصدار: ${x}`,
IsLatest: "أحدث إصدار",
CheckUpdate: "التحقق من التحديث",
IsChecking: "جارٍ التحقق من التحديث...",
FoundUpdate: (x: string) => ` تم العثور على إصدار جديد: ${x}`,
GoToUpdate: "التحديث",
Version: (x: string) => `الإصدار الحالي: ${x}`,
IsLatest: نت على أحدث إصدار",
CheckUpdate: "التحقق من التحديثات",
IsChecking: "جارٍ التحقق من التحديثات...",
FoundUpdate: (x: string) => `تم العثور على إصدار جديد: ${x}`,
GoToUpdate: "انتقل للتحديث",
},
SendKey: "مفتاح الإرسال",
SendKey: "زر الإرسال",
Theme: "السمة",
TightBorder: "حدود ضيقة",
TightBorder: "وضع بدون حدود",
SendPreviewBubble: {
Title: "عرض معاينة الـ Send",
SubTitle: "معاينة Markdown في فقاعة",
Title: "فقاعة المعاينة",
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: {
Splash: {
Title: "شاشة تظهر الأقنعة",
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة",
Title: "صفحة بدء القناع",
SubTitle: "عرض صفحة بدء القناع عند بدء دردشة جديدة",
},
Builtin: {
Title: "إخفاء الأقنعة المدمجة",
SubTitle: "إخفاء الأقنعة المدمجة في قائمة الأقنعة",
},
},
Prompt: {
Disable: {
Title: "تعطيل الاكتمال التلقائي",
SubTitle: "اكتب / لتشغيل الاكتمال التلقائي",
Title: "تعطيل الإكمال التلقائي للإشعارات",
SubTitle: "استخدم / في بداية مربع النص لتفعيل الإكمال التلقائي",
},
List: "قائمة الاقتراحات",
ListCount: (builtin: number, custom: number) => `
${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخدم`,
Edit: عديل",
List: "قائمة الإشعارات المخصصة",
ListCount: (builtin: number, custom: number) =>
`مدمج ${builtin} إشعار، مخصص ${custom} إشعار`,
Edit: حرير",
Modal: {
Title: "قائمة الاقتراحات",
Add: "إضافة واحدة",
Search: "البحث في الاقتراحات",
Title: "قائمة الإشعارات",
Add: "جديد",
Search: "بحث عن إشعارات",
},
EditModal: {
Title: "تحرير الاقتراح",
Title: "تحرير الإشعارات",
},
},
HistoryCount: {
Title: "عدد الرسائل المرفقة",
SubTitle: "عدد الرسائل المرسلة المرفقة في كل طلب",
Title: "عدد الرسائل التاريخية المرفقة",
SubTitle: "عدد الرسائل التاريخية المرفقة مع كل طلب",
},
CompressThreshold: {
Title: "حد الضغط للتاريخ",
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
Title: "عتبة ضغط طول الرسائل التاريخية",
SubTitle:
"عندما يتجاوز طول الرسائل التاريخية غير المضغوطة هذه القيمة، سيتم الضغط",
},
Usage: {
Title: "رصيد الحساب",
Title: "التحقق من الرصيد",
SubTitle(used: any, total: any) {
return `تم استخدام $${used} من هذا الشهر، الاشتراك ${total}`;
return `تم استخدام $${used} هذا الشهر، إجمالي الاشتراك $${total}`;
},
IsChecking: "جارٍ التحقق...",
Check: "التحقق",
NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
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: "أدخل مفتاح 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: "النموذج",
Temperature: {
Title: "الحرارة",
SubTitle: "قيمة أكبر تجعل الإخراج أكثر عشوائية",
Title: "العشوائية (temperature)",
SubTitle: "كلما زادت القيمة، زادت العشوائية في الردود",
},
TopP: {
Title: "عينات النواة (top_p)",
SubTitle: "مشابه للعشوائية ولكن لا تغيره مع العشوائية",
},
MaxTokens: {
Title: "الحد الأقصى للرموز",
SubTitle: "الحد الأقصى لعدد الرموز المدخلة والرموز المُنشأة",
Title: "حد أقصى للرموز لكل رد (max_tokens)",
SubTitle: "أقصى عدد للرموز في تفاعل واحد",
},
PresencePenalty: {
Title: أثير الوجود",
SubTitle: "قيمة أكبر تزيد من احتمالية التحدث عن مواضيع جديدة",
Title: جدد الموضوع (presence_penalty)",
SubTitle: "كلما زادت القيمة، زادت احتمالية التوسع في مواضيع جديدة",
},
FrequencyPenalty: {
Title: "تأثير التكرار",
SubTitle: "قيمة أكبر تقلل من احتمالية تكرار نفس السطر",
Title: "عقوبة التكرار (frequency_penalty)",
SubTitle: "كلما زادت القيمة، زادت احتمالية تقليل تكرار الكلمات",
},
},
Store: {
DefaultTopic: "محادثة جديدة",
BotHello: "مرحبًا! كيف يمكنني مساعدتك اليوم؟",
Error: "حدث خطأ ما، يرجى المحاولة مرة أخرى في وقت لاحق.",
DefaultTopic: "دردشة جديدة",
BotHello: "كيف يمكنني مساعدتك؟",
Error: "حدث خطأ، يرجى المحاولة مرة أخرى لاحقًا",
Prompt: {
History: (content: string) => "هذا ملخص لسجل الدردشة كمراجعة: " + content,
History: (content: string) =>
"هذا ملخص للدردشة السابقة كنقطة انطلاق: " + content,
Topic:
"يرجى إنشاء عنوان يتكون من أربع إلى خمس كلمات يلخص محادثتنا دون أي مقدمة أو ترقيم أو علامات ترقيم أو نقاط أو رموز إضافية. قم بإزالة علامات التنصيص المحيطة.",
"استخدم أربع إلى خمس كلمات لإرجاع ملخص مختصر لهذه الجملة، بدون شرح، بدون علامات ترقيم، بدون كلمات تعبيرية، بدون نص إضافي، بدون تنسيق عريض، إذا لم يكن هناك موضوع، يرجى العودة إلى 'دردشة عامة'",
Summarize:
"قم بتلخيص النقاش بشكل موجز في 200 كلمة أو أقل لاستخدامه كاقتراح للسياق في المستقبل.",
"قم بتلخيص محتوى الدردشة باختصار، لاستخدامه كإشارة سياقية لاحقة، اجعلها في حدود 200 كلمة",
},
},
Copy: {
Success: "تم النسخ إلى الحافظة",
Failed: "فشلت عملية النسخ، يرجى منح الإذن للوصول إلى الحافظة",
Success: "تم الكتابة إلى الحافظة",
Failed: "فشل النسخ، يرجى منح أذونات الحافظة",
},
Download: {
Success: "تم تنزيل المحتوى إلى مجلدك.",
Failed: "فشل التنزيل.",
},
Context: {
Toast: (x: any) => `مع ${x} اقتراحًا ذا سياق`,
Edit: "الاقتراحات السياقية والذاكرة",
Add: "إضافة اقتراح",
Clear: "مسح السياق",
Revert: "التراجع",
Toast: (x: any) => `يحتوي على ${x} إشعارات مخصصة`,
Edit: "إعدادات الدردشة الحالية",
Add: "إضافة دردشة جديدة",
Clear: "تم مسح السياق",
Revert: "استعادة السياق",
},
Plugin: {
Name: "المكوّن الإضافي",
Name: "الإضافات",
},
FineTuned: {
Sysmessage: "أنت مساعد ي",
Sysmessage: "أنت مساعد",
},
Mask: {
Name: "الأقنعة",
SearchChat: {
Name: "بحث",
Page: {
Title: "قالب الاقتراح",
SubTitle: (count: number) => `${count} قوالب الاقتراح`,
Search: "البحث في القوالب",
Create: "إنشاء",
Title: "البحث في سجلات الدردشة",
Search: "أدخل كلمات البحث",
NoResult: "لم يتم العثور على نتائج",
NoData: "لا توجد بيانات",
Loading: "جارٍ التحميل",
SubTitle: (count: number) => `تم العثور على ${count} نتائج`,
},
Item: {
Info: (count: number) => `${count} اقتراحات`,
View: "عرض",
},
},
Mask: {
Name: "القناع",
Page: {
Title: "أقنعة الأدوار المخصصة",
SubTitle: (count: number) => `${count} تعريف لدور مخصص`,
Search: "بحث عن قناع الدور",
Create: "إنشاء جديد",
},
Item: {
Info: (count: number) => `يحتوي على ${count} محادثات مخصصة`,
Chat: "الدردشة",
View: "عرض",
Edit: عديل",
Edit: حرير",
Delete: "حذف",
DeleteConfirm: "تأكيد الحذف؟",
},
EditModal: {
Title: (readonly: boolean) => `
تعديل قالب الاقتراح ${readonly ? "(للقراءة فقط)" : ""}`,
Download: "تنزيل",
Clone: "استنساخ",
Title: (readonly: boolean) =>
`تحرير القناع المخصص ${readonly ? " (للقراءة فقط)" : ""}`,
Download: "تنزيل القناع المخصص",
Clone: "استنساخ القناع",
},
Config: {
Avatar: "صورة الروبوت",
Name: "اسم الروبوت",
Avatar: "صورة الدور",
Name: "اسم الدور",
Sync: {
Title: "استخدام الإعدادات العامة",
SubTitle: "استخدام الإعدادات العامة في هذه الدردشة",
Confirm: "تأكيد الاستبدال بالإعدادات المخصصة بالإعدادات العامة؟",
Title: "استخدام الإعدادات العالمية",
SubTitle: "هل تستخدم الدردشة الحالية الإعدادات العالمية للنموذج",
Confirm:
"ستتم الكتابة فوق الإعدادات المخصصة للدردشة الحالية تلقائيًا، تأكيد تفعيل الإعدادات العالمية؟",
},
HideContext: {
Title: "إخفاء اقتراحات السياق",
SubTitle: "عدم عرض اقتراحات السياق في الدردشة",
Title: "إخفاء المحادثات المخصصة",
SubTitle: "بعد الإخفاء، لن تظهر المحادثات المخصصة في واجهة الدردشة",
},
Share: {
Title: "مشاركة هذا القناع",
SubTitle: "إنشاء رابط مباشر لهذا القناع",
Action: "نسخ الرابط",
},
},
},
NewChat: {
Return: "العودة",
Skip: "ابدأ فقط",
Title: "اختيار قناع",
SubTitle: "دردشة مع الروح وراء القناع",
More: "المزيد",
Skip: "بدء الآن",
NotShow: "عدم العرض مرة أخرى",
ConfirmNoShow: "تأكيد تعطيله؟ يمكنك تمكينه في الإعدادات لاحقًا.",
ConfirmNoShow:
"تأكيد إلغاء العرض؟ بعد الإلغاء، يمكنك إعادة تفعيله في الإعدادات في أي وقت.",
Title: "اختر قناعًا",
SubTitle: "ابدأ الآن وتفاعل مع الأفكار خلف القناع",
More: "عرض الكل",
},
URLCommand: {
Code: "تم الكشف عن رمز وصول في الرابط، هل تريد تعبئته تلقائيًا؟",
Settings: "تم الكشف عن إعدادات مسبقة في الرابط، هل تريد تعبئتها تلقائيًا؟",
},
UI: {
@@ -276,9 +537,16 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
Cancel: "إلغاء",
Close: "إغلاق",
Create: "إنشاء",
Edit: عديل",
Edit: حرير",
Export: "تصدير",
Import: "استيراد",
Sync: "مزامنة",
Config: "تكوين",
},
Exporter: {
Description: {
Title: "فقط الرسائل بعد مسح السياق سيتم عرضها",
},
Model: "النموذج",
Messages: "الرسائل",
Topic: "الموضوع",

View File

@@ -1,332 +1,564 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "./index";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const bn: PartialLocaleType = {
WIP: "শীঘ্রই আসছে...",
Error: {
Unauthorized:
"অননুমোদিত অ্যাক্সেস, অনুগ্রহ করে [অথোরাইজশন](/#/auth) পৃষ্ঠায় অ্যাক্সেস কোড ইনপুট করুন।",
Unauthorized: isApp
? "অবৈধ API কী সনাক্ত করা হয়েছে, অনুগ্রহ করে [সেটিংস](/#/settings) পৃষ্ঠায় যান এবং নিশ্চিত করুন যে API কী সঠিকভাবে কনফিগার করা হয়েছে।"
: "অ্যাক্সেস পাসওয়ার্ড সঠিক নয় বা খালি, অনুগ্রহ করে [লগইন](/#/auth) পৃষ্ঠায় যান এবং সঠিক অ্যাক্সেস পাসওয়ার্ড প্রবেশ করান, অথবা [সেটিংস](/#/settings) পৃষ্ঠায় আপনার OpenAI API কী প্রবেশ করান।",
},
Auth: {
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
SubTips: "অথবা আপনার OpenAI API কী প্রবেশ করন",
Input: "অ্যাক্সেস কোড",
Title: "পাসওয়ার্ড প্রয়োজন",
Tips: "অ্যাডমিন পাসওয়ার্ড প্রমাণীকরণ চালু করেছেন, নিচে অ্যাক্সেস কোড প্রবেশ করুন",
SubTips: "অথবা আপনার OpenAI অথবা Google API কী প্রবেশ করন",
Input: "এখানে অ্যাক্সেস কোড লিখুন",
Confirm: "নিশ্চিত করুন",
Later: "পরে",
Later: "পরে বলুন",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} টি বার্তা`,
ChatItemCount: (count: number) => `${count} টি চ্যাট`,
},
Chat: {
SubTitle: (count: number) => `${count} টি বার্তা`,
SubTitle: (count: number) => `মোট ${count} টি চ্যাট`,
EditMessage: {
Title: "বার্তাগুলি সম্পাদনা করুন",
Topic: {
Title: "চ্যাটের বিষয়",
SubTitle: "বর্তমান চ্যাটের বিষয় পরিবর্তন করুন",
},
},
Actions: {
ChatList: "চ্যাট তালিকায় যান",
CompressedHistory: "সংক্ষিপ্ত ইতিহাস মেমোরি প্রম্পট",
Export: "সমস্ত বার্তা মার্কডাউন হিসাবে রপ্তানি করুন",
Copy: "কপি",
Stop: "বন্ধ করুন",
ChatList: "বার্তা তালিকা দেখুন",
CompressedHistory: "সংকুচিত ইতিহাস দেখুন",
Export: "চ্যাট ইতিহাস রপ্তান করুন",
Copy: "অনুলিপি করুন",
Stop: "থামান",
Retry: "পুনরায় চেষ্টা করুন",
Pin: "পিন করুন",
PinToastContent: "পিন করা হয়েছে ২টি বার্তা প্রম্পটে",
PinToastContent: "1 টি চ্যাট পূর্বনির্ধারিত প্রম্পটে পিন করা হয়েছে",
PinToastAction: "দেখুন",
Delete: "মুছে ফেলুন",
Edit: "সম্পাদন করুন",
Edit: "সম্পাদন করুন",
},
Commands: {
new: "নতুন চ্যাট শুরু করুন",
newm: "মাস্ক সহ নতুন চ্যাট শুরু করুন",
new: "নতুন চ্যাট",
newm: "মাস্ক থেকে নতুন চ্যাট",
next: "পরবর্তী চ্যাট",
prev: "পূর্ববর্তী চ্যাট",
clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
del: "চ্যাট মুছুন",
clear: "প্রসঙ্গ পরিষ্কার করুন",
del: "চ্যাট মুছে ফেলুন",
},
InputActions: {
Stop: "বন্ধ করুন",
ToBottom: "সর্বশেষতম দিকে",
Stop: "প্রতিক্রিয়া থামান",
ToBottom: "সর্বশেষে স্ক্রোল করুন",
Theme: {
auto: "অটো",
light: "হালকা থিম",
dark: "ডার্ক থিম",
auto: "স্বয়ংক্রিয় থিম",
light: "আলোর মোড",
dark: "অন্ধকার মোড",
},
Prompt: "প্রম্পটগুলিতে",
Masks: "মাস্কগুলি",
Clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
Settings: "সেটিংস",
Prompt: "সংক্ষিপ্ত নির্দেশনা",
Masks: "সমস্ত মাস্ক",
Clear: "চ্যাট পরিষ্কার করুন",
Settings: "চ্যাট সেটিংস",
UploadImage: "চিত্র আপলোড করুন",
},
Rename: "চ্যাট পুনঃনামকরণ করুন",
Typing: "টাইপিং...",
Rename: "চ্যাট নাম পরিবর্তন করুন",
Typing: "লিখছে…",
Input: (submitKey: string) => {
var inputHints = `${submitKey} to send`;
var inputHints = `${submitKey} পাঠান`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter to wrap";
inputHints += "Shift + Enter নতুন লাইন";
}
return inputHints + ", / to search prompts, : to use commands";
return inputHints + "/ পূর্ণতা সক্রিয় করুন,: কমান্ড সক্রিয় করুন";
},
Send: "প্রেরণ করুন",
Send: "পাঠান",
Config: {
Reset: "ডিফল্টে রিসেট করুন",
Reset: "মেমরি মুছে ফেলুন",
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
},
IsContext: "পূর্বনির্ধারিত প্রম্পট",
},
Export: {
Title: "বার্তা রপ্তানিকরণ",
Copy: "সমস্তটি কপি করুন",
Download: "ডাউনলোড করুন",
MessageFromYou: "আপনার বার্তা",
MessageFromChatGPT: "্যাটজিপিটির বার্তা",
Share: "শেয়ার করুন শেয়ারজিপিটি তে",
Title: "চ্যাট ইতিহাস শেয়ার করুন",
Copy: "সবকিছু কপি করুন",
Download: "ফাইল ডাউনলোড করুন",
Share: "ShareGPT তে শেয়ার করুন",
MessageFromYou: "্যবহারকারী",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "রপ্তানি ফরম্যাট",
SubTitle: "মার্কডাউন বা পিএনজি চিত্র",
Title: "রপ্তানি ফরম্যাট",
SubTitle: "Markdown টেক্সট বা PNG চিত্র রপ্তানি করা যাবে",
},
IncludeContext: {
Title: "মাস্ক অন্তর্ভুক্ত করুন",
SubTitle: "মাস্কগুলি সংরক্ষণ করবেন না কি",
Title: "মাস্ক প্রসঙ্গ অন্তর্ভুক্ত করুন",
SubTitle: "বার্তায় মাস্ক প্রসঙ্গ প্রদর্শন করা হবে কি না",
},
Steps: {
Select: "নির্বাচন করুন",
Preview: "প্রিভিউ করুন",
Preview: "পূর্বরূপ দেখুন",
},
Image: {
Toast: "স্ক্রীনশট তৈরি করা হচ্ছে",
Modal: "ছবি সংরক্ষণ করতে দীর্ঘ প্রেস করুন অথবা রাইট ক্লিক করুন",
},
},
Select: {
Search: "অনুসন্ধান করুন",
All: "সমস্তটি নির্বাচন করুন",
Latest: "সর্বশেষতমটি নির্বাচন করুন",
Clear: "পরিষ্কার করুন",
Search: "বার্তা অনুসন্ধান করুন",
All: "সবকিছু নির্বাচন করুন",
Latest: "সর্বশেষ কিছু",
Clear: "নির্বাচন পরিষ্কার করুন",
},
Memory: {
Title: "মেমোরি প্রম্পট",
EmptyContent: "এখনও কিছুই নেই",
Send: "মেমোরি প্রেরণ করুন",
Copy: "মেমোরি কপি করুন",
Reset: "পুনরায় নিশ্চিত করুন",
ResetConfirm:
"রিসেট করলে বর্তমান চ্যাট ইতিহাস এবং ঐতিহাসিক মেমোরি মুছে যাবে। পুনরায় নির্দিষ্ট করতে চান তা নিশ্চিত করতে চান?",
Title: "ইতিহাস সারাংশ",
EmptyContent: "চ্যাটের বিষয়বস্তু খুব সংক্ষিপ্ত, সারাংশ প্রয়োজন নেই",
Send: "অটোমেটিক চ্যাট ইতিহাস সংকুচিত করুন এবং প্রসঙ্গ হিসেবে পাঠান",
Copy: "সারাংশ কপি করুন",
Reset: "[unused]",
ResetConfirm: "ইতিহাস সারাংশ মুছে ফেলার নিশ্চিত করুন?",
},
Home: {
NewChat: "নতুন চ্যাট",
DeleteChat: "নির্বাচিত সংলাপটি মুছতে নিশ্চিত করুন?",
DeleteToast: "চ্যাটটি মুছেছে",
Revert: "পুনরায়",
DeleteChat: "নির্বাচিত চ্যাট মুছে ফেলার নিশ্চিত করুন?",
DeleteToast: "চ্যাট মুছে ফেলা হয়েছে",
Revert: "পূর্বাবস্থায় ফেরান",
},
Settings: {
Title: "সেটিংস",
SubTitle: "সমস্ত সেটিংস",
SubTitle: "সমস্ত সেটিংস অপশন",
Danger: {
Reset: {
Title: "সমস্ত সেটিংস পুনঃনির্দেশ দিন",
SubTitle: "সকল সেটিংস ডিফল্টে পুনঃনির্দেশ দিতে",
Action: "পুনঃনির্দেশ দিন",
Confirm: "সমস্ত সেটিংস ডিফল্টে পুনঃনির্দেশ করতে নিশ্চিত করতে?",
Title: "সমস্ত সেটিংস পুনরায় সেট করুন",
SubTitle: "সমস্ত সেটিংস বিকল্পগুলিকে ডিফল্ট মানে পুনরায় সেট করুন",
Action: "এখনই পুনরায় সেট করুন",
Confirm: "সমস্ত সেটিংস পুনরায় সেট করার নিশ্চিত করুন?",
},
Clear: {
Title: "সমস্ত তথ্য মুছুন",
SubTitle: "সমস্ত বার্তা এবং সেটিংস মুছুন",
Action: "মুছুন",
Confirm: "সমস্ত বার্তা এবং সেটিংস মুছে ফেলতে নিশ্চিত করতে?",
Title: "সমস্ত তথ্য মুছে ফেলুন",
SubTitle: "সমস্ত চ্যাট এবং সেটিংস ডেটা মুছে ফেলুন",
Action: "এখনই মুছে ফেলুন",
Confirm: "সমস্ত চ্যাট এবং সেটিংস ডেটা মুছে ফেলানোর নিশ্চিত করুন?",
},
},
Lang: {
Name: "বাংলা", // 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: "সমস্ত ভাষা",
},
Avatar: "অতা",
Avatar: "অভিনেতা",
FontSize: {
Title: "ফন্ট সাইজ",
SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন",
SubTitle: "চ্যাট কনটেন্টের ফন্ট সাইজ",
},
FontFamily: {
Title: "চ্যাট ফন্ট",
SubTitle:
"চ্যাট সামগ্রীর ফন্ট, বিশ্বব্যাপী ডিফল্ট ফন্ট প্রয়োগ করতে খালি রাখুন",
Placeholder: "ফন্টের নাম",
},
InjectSystemPrompts: {
Title: "حقن تلميحات النظام",
Title: "সিস্টেম-লেভেল প্রম্পট যোগ করুন",
SubTitle:
"قم بإضافة تلميحة نظام محاكاة ChatGPT إلى بداية قائمة الرسائل المُطلَبة في كل طلب",
"প্রত্যেক বার্তায় একটি সিস্টেম প্রম্পট যোগ করুন যা ChatGPT এর অনুকরণ করবে",
},
InputTemplate: {
Title: "ইনপুট টেমপ্লেট",
SubTitle: "নতুনতম বার্তা এই টেমপ্লেটে পূরণ হবে",
Title: "ব্যবহারকারীর ইনপুট প্রিপ্রসেসিং",
SubTitle: "ব্যবহারকারীর সর্বশেষ বার্তা এই টেমপ্লেটে পূরণ করা হবে",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Latest version",
CheckUpdate: "Check Update",
IsChecking: "Checking update...",
FoundUpdate: (x: string) => `Found new version: ${x}`,
GoToUpdate: "Update",
Version: (x: string) => `বর্তমান সংস্করণ: ${x}`,
IsLatest: "এটি সর্বশেষ সংস্করণ",
CheckUpdate: "আপডেট পরীক্ষা করুন",
IsChecking: "আপডেট পরীক্ষা করা হচ্ছে...",
FoundUpdate: (x: string) => `নতুন সংস্করণ পাওয়া গিয়েছে: ${x}`,
GoToUpdate: "আপডেট করতে যান",
},
SendKey: "প্রেরণ চাবি",
SendKey: "পাঠানোর কী",
Theme: "থিম",
TightBorder: "সঙ্গতি সীমা",
TightBorder: "বর্ডার-বিহীন মোড",
SendPreviewBubble: {
Title: "প্রিভিউ বুবুল প্রেরণ করুন",
SubTitle: "বুবুমার্কডাউন প্রিভিউ করুন",
Title: "প্রিভিউ বুবু",
SubTitle: "প্রিভিউ বুবু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 টোকেন",
},
},
LocalState: "স্থানীয় ডেটা",
Overview: (overview: any) => {
return `${overview.chat} বার চ্যাট, ${overview.message} বার্তা, ${overview.prompt} প্রম্পট, ${overview.mask} মাস্ক`;
},
ImportFailed: "আমদানি ব্যর্থ",
},
Mask: {
Splash: {
Title: "মাস্ক স্প্ল্যাশ স্ক্রিন",
SubTitle:
"নতুন চ্যাট শুরু করার আগে মাস্ক স্প্ল্যাশ স্ক্রিন প্রদর্শন করুন",
Title: "মাস্ক লঞ্চ পেজ",
SubTitle: "নতুন চ্যাট শুরু করার সময় মাস্ক লঞ্চ পেজ প্রদর্শন করুন",
},
Builtin: {
Title: "মূলত মাস্ক গোপন করুন",
SubTitle: "মাস্ক তালিকা থেকে মূলত মাস্কগুলি লুকান",
Title: "ইনবিল্ট মাস্ক লুকান",
SubTitle: "সমস্ত মাস্ক তালিকায় ইনবিল্ট মাস্ক লুকান",
},
},
Prompt: {
Disable: {
Title: "অটো-সম্পূর্ণতা নিষ্ক্রিয় করুন",
SubTitle: "অটো-সম্পূর্ণতা চালু করতে / ইনপুট করুন",
Title: "প্রম্পট অটো-কমপ্লিশন নিষ্ক্রিয় করুন",
SubTitle: "ইনপুট বক্সের শুরুতে / টাইপ করলে অটো-কমপ্লিশন সক্রিয় হবে",
},
List: "প্রম্পট তালিকা",
List: "স্বনির্ধারিত প্রম্পট তালিকা",
ListCount: (builtin: number, custom: number) =>
`${builtin} built-in, ${custom} user-defined`,
Edit: "সম্পাদন করুন",
`ইনবিল্ট ${builtin} টি, ব্যবহারকারী সংজ্ঞায়িত ${custom} টি`,
Edit: "সম্পাদন করুন",
Modal: {
Title: "প্রম্পট তালিকা",
Add: "একটি যোগ করুন",
Search: "সন্ধান প্রম্পট",
Add: "নতুন করুন",
Search: "প্রম্পট অনুসন্ধান করুন",
},
EditModal: {
Title: "সম্পাদন করুন প্রম্পট",
Title: "প্রম্পট সম্পাদন করুন",
},
},
HistoryCount: {
Title: "সংযুক্ত বার্তা সংখ্যা",
SubTitle: "প্রতি অনুরোধে প্রেরণ করা গেলে প্রেরণ করা হবে",
Title: "সংযুক্ত ইতিহাস বার্তা সংখ্যা",
SubTitle: "প্রতিটি অনুরোধে সংযুক্ত ইতিহাস বার্তার সংখ্যা",
},
CompressThreshold: {
Title: "ইতিহাস সঙ্কুচিত করার সীমা",
Title: "ইতিহাস বার্তা দৈর্ঘ্য সংকুচিত থ্রেশহোল্ড",
SubTitle:
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
"যখন সংকুচিত ইতিহাস বার্তা এই মান ছাড়িয়ে যায়, তখন সংকুচিত করা হবে",
},
Usage: {
Title: "একাউন্ট ব্যালেন্স",
Title: "ব্যালেন্স চেক",
SubTitle(used: any, total: any) {
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন $${total}`;
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন মোট $${total}`;
},
IsChecking: "চেক করা হচ্ছে...",
Check: "চেক",
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
IsChecking: "পরীক্ষা করা হচ্ছে",
Check: "পুনরায় পরীক্ষা করুন",
NoAccess: "ব্যালেন্স দেখতে API কী অথবা অ্যাক্সেস পাসওয়ার্ড প্রবেশ করুন",
},
Model: "মডেল",
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: "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: {
Title: "তাপমাত্রা",
SubTitle: "আরতি মান বেশি করলে বেশি এলোমেলো আউটপুট হবে",
Title: "যাদুকরিতা (temperature)",
SubTitle: "মান বাড়ালে উত্তর বেশি এলোমেলো হবে",
},
TopP: {
Title: "শীর্ষ পি",
SubTitle: "তাপমাত্রা সঙ্গে এই মান পরিবর্তন করবেন না",
Title: "নিউক্লিয়ার স্যাম্পলিং (top_p)",
SubTitle: "যাদুকরিতা মত, কিন্তু একসাথে পরিবর্তন করবেন না",
},
MaxTokens: {
Title: "সর্বাধিক টোকেন",
SubTitle: "ইনপুট টোকেন এবং উৎপাদিত টোকেনে সর্বাধিক দৈর্ঘ্য",
Title: "একটি উত্তর সীমা (max_tokens)",
SubTitle: "প্রতি ইন্টারঅ্যাকশনে সর্বাধিক টোকেন সংখ্য",
},
PresencePenalty: {
Title: "উপস্থিতির জরিমানা",
SubTitle: "আরতি মান বেশি করলে নতুন বিষয়গুলি সম্ভাব্যতা বাড়াতে পারে",
Title: "বিষয়বস্তু তাজা (presence_penalty)",
SubTitle: "মান বাড়ালে নতুন বিষয়ে প্রসারিত হওয়ার সম্ভাবনা বেশি",
},
FrequencyPenalty: {
Title: "ফ্রিকয়েন্সি জরিমানা",
SubTitle:
"আরতি মান বাড়ালে একই লাইন পুনরায় ব্যাবহার করার সম্ভাবনা হ্রাস পায়",
Title: "ফ্রিকয়েন্সি পেনাল্টি (frequency_penalty)",
SubTitle: "মান বাড়ালে পুনরাবৃত্তি শব্দ কমানোর সম্ভাবনা বেশি",
},
},
Store: {
DefaultTopic: "নতুন সংলাপ",
BotHello: "হ্যালো! আজকে আপনাকে কিভাবে সাহায্য করতে পারি?",
Error: "কিছু নিয়ে ভুল হয়েছে, পরে আবার চেষ্টা করুন",
DefaultTopic: "নতুন চ্যাট",
BotHello: "আপনার জন্য কিছু করতে পারি?",
Error: "একটি ত্রুটি ঘটেছে, পরে আবার চেষ্টা করুন",
Prompt: {
History: (content: string) =>
"এটি চ্যাট ইতিহাসের সংক্ষিপ্ত সংকলনের মতো: " + content,
"এটি পূর্বের চ্যাটের সারাংশ হিসেবে ব্যবহৃত হবে: " + content,
Topic:
"আমাদের সংলাপটির চার থেকে পাঁচ শব্দের একটি শিরোনাম তৈরি করুন যা আমাদের আলাপের সংক্ষিপ্তসার হিসাবে যোগ হবে না, যেমন অভিবৃত্তি, বিন্যাস, উদ্ধৃতি, পূর্বচালক চিহ্ন, পূর্বরোবক্তির যেকোনো চিহ্ন বা অতিরিক্ত পাঠ। মেয়াদশেষ উদ্ধৃতি চেষ্টা করুন",
"চার থেকে পাঁচটি শব্দ ব্যবহার করে এই বাক্যের সংক্ষিপ্ত থিম দিন, ব্যাখ্যা, বিরাম চিহ্ন, ভাষা, অতিরিক্ত টেক্সট বা বোল্ড না ব্যবহার করুন। যদি কোনো থিম না থাকে তবে সরাসরি 'বেকার' বলুন",
Summarize:
"২০০ শব্দের লম্বা হয়ে মুহূর্তে আলোচনা সংক্ষেপের রপ্তানি করুন, যেটি ভবিষ্যতের প্রম্পট হিসবে ব্যবহার করবেন।",
"আলোচনার বিষয়বস্তু সংক্ষিপ্তভাবে সারাংশ করুন, পরবর্তী কনটেক্সট প্রম্পট হিসবে ব্যবহারের জন্য, ২০০ শব্দের মধ্যে সীমাবদ্ধ রাখুন",
},
},
Copy: {
Success: "ক্লিপবোর্ডে কপি করা হয়েছে",
Failed: "কপি ব্যর্থ, অনুমতি প্রদান করার জন্য অনুমতি প্রদান করুন",
Success: "ক্লিপবোর্ডে লেখা হয়েছে",
Failed: "কপি ব্যর্থ হয়েছে, দয়া করে ক্লিপবোর্ড অনুমতি প্রদান করুন",
},
Download: {
Success: "বিষয়বস্তু আপনার ডিরেক্টরিতে ডাউনলোড করা হয়েছে।",
Failed: "ডাউনলোড ব্যর্থ হয়েছে।",
},
Context: {
Toast: (x: any) => `With ${x} contextual prompts`,
Toast: (x: any) => `${x}টি পূর্বনির্ধারিত প্রম্পট অন্তর্ভুক্ত`,
Edit: "বর্তমান চ্যাট সেটিংস",
Add: "একটি প্রম্পট যোগ করুন",
Clear: "সঙ্গতি পরি্কার করুন",
Revert: "পূর্ববর্তী অবস্থানে ফিরে যান",
Add: "একটি নতুন চ্যাট যোগ করুন",
Clear: "কনটেক্সট পরি্কার করা হয়েছে",
Revert: "কনটেক্সট পুনরুদ্ধার করুন",
},
Plugin: {
Name: "প্লাগইন",
},
FineTuned: {
Sysmessage: "আপনি একটি সহকারী যা",
Sysmessage: "আপনি একজন সহকারী",
},
SearchChat: {
Name: "অনুসন্ধান",
Page: {
Title: "চ্যাট রেকর্ড অনুসন্ধান করুন",
Search: "অনুসন্ধান কীওয়ার্ড লিখুন",
NoResult: "কোন ফলাফল পাওয়া যায়নি",
NoData: "কোন তথ্য নেই",
Loading: "লোড হচ্ছে",
SubTitle: (count: number) => `${count} টি ফলাফল পাওয়া গেছে`,
},
Item: {
View: "দেখুন",
},
},
Mask: {
Name: "মাস্ক",
Page: {
Title: "প্রম্পট টেমপ্লেট",
SubTitle: (count: number) => `${count} টি প্রম্পট টেমপ্লেট`,
Search: "টেমপ্লেট অনুসন্ধান করুন",
Create: "তৈরি করুন",
Title: "পূর্বনির্ধারিত চরিত্র মাস্ক",
SubTitle: (count: number) => `${count}টি পূর্বনির্ধারিত চরিত্র সংজ্ঞা`,
Search: "চরিত্র মাস্ক অনুসন্ধান করুন",
Create: "নতুন তৈরি করুন",
},
Item: {
Info: (count: number) => `${count} প্রম্পট`,
Info: (count: number) => `ভিতরে ${count}টি পূর্বনির্ধারিত চ্যাট রয়েছে`,
Chat: "চ্যাট",
View: "দেখুন",
Edit: "সম্পাদন করুন",
Edit: "সম্পাদন করুন",
Delete: "মুছে ফেলুন",
DeleteConfirm: "মুছে ফেলতে নিশ্চিত করুন?",
DeleteConfirm: "মুছে ফেলার জন্য নিশ্চিত করুন?",
},
EditModal: {
Title: (readonly: boolean) =>
`্রম্পট টেমপ্লেট সম্পাদন করুন ${readonly ? "(readonly)" : ""}`,
Download: "ডাউনলোড করুন",
Clone: "ক্লোন করুন",
`ূর্বনির্ধারিত মাস্ক সম্পাদন ${readonly ? "(পঠনযোগ্য)" : ""}`,
Download: "পূর্বনির্ধারিত ডাউনলোড করুন",
Clone: "পূর্বনির্ধারিত ক্লোন করুন",
},
Config: {
Avatar: "বট অবতার",
Name: "বটের নাম",
Avatar: "চরিত্রের চিত্র",
Name: "চরিত্রের নাম",
Sync: {
Title: "গ্লোবাল কনফিগ ব্যবহার করুন",
SubTitle: "এই চ্যাট গ্লোবাল কনফিগ ব্যবহার করুন",
Title: "গ্লোবাল সেটিংস ব্যবহার করুন",
SubTitle: "বর্তমান চ্যাট গ্লোবাল মডেল সেটিংস ব্যবহার করছে কি না",
Confirm:
"গ্লোবাল কনফিগ দ্বারা কাস্টম কনফিগ ওভাররাইড করতে নিশ্চিত করতে?",
"বর্তমান চ্যাটের কাস্টম সেটিংস স্বয়ংক্রিয়ভাবে ওভাররাইট হবে, গ্লোবাল সেটিংস সক্রিয় করতে নিশ্চিত?",
},
HideContext: {
Title: "সংশ্লিষ্টতা প্রম্পটগুলি লুকান",
SubTitle: "চ্যাটে সংশ্লিষ্টতা প্রম্পটগুলি দেখাবেন না",
Title: "পূর্বনির্ধারিত চ্যাট লুকান",
SubTitle:
"লুকানোর পরে পূর্বনির্ধারিত চ্যাট চ্যাট ইন্টারফেসে প্রদর্শিত হবে না",
},
Share: {
Title: "এই মাস্কটি শেয়ার করুন",
SubTitle: "এই মাস্কের একটি লিঙ্ক তৈরি করুন",
Title: "এই মাস্ক শেয়ার করুন",
SubTitle: "এই মাস্কের সরাসরি লিঙ্ক তৈরি করুন",
Action: "লিঙ্ক কপি করুন",
},
},
},
NewChat: {
Return: "ফিরে যান",
Skip: "শুরু করুন",
Title: "মাস্ক নির্বাচন করুন",
SubTitle: "মাস্কের পিছনে আত্মার সঙ্গে চ্যাট করুন",
More: "আরো খুঁজুন",
NotShow: "এখনও দেখাবেন না",
Skip: "ডাইরেক্ট শুরু করুন",
NotShow: "আবার প্রদর্শন করবেন না",
ConfirmNoShow:
"নিষ্ক্রিয় করতে নিশ্চিত করুন? পরে আপনি এটি সেটিংসে সক্ষম করতে পারবে।",
"নিশ্চিত যে নিষ্ক্রিয় করবেন? নিষ্ক্রিয় করার পরে সেটিংসে পুনরায় সক্রিয় করা যাবে।",
Title: "একটি মাস্ক নির্বাচন করুন",
SubTitle: "এখন শুরু করুন, মাস্কের পিছনের চিন্তা প্রতিক্রিয়া করুন",
More: "সব দেখুন",
},
URLCommand: {
Code: "লিঙ্কে অ্যাক্সেস কোড ইতিমধ্যে অন্তর্ভুক্ত রয়েছে, অটো পূরণ করতে চান?",
Settings:
"লিঙ্কে প্রাক-নির্ধারিত সেটিংস অন্তর্ভুক্ত রয়েছে, অটো পূরণ করতে চান?",
},
UI: {
Confirm: "নিশ্চিত করুন",
Cancel: "বাতিল করুন",
Close: "বন্ধ করুন",
Create: "তৈরি করুন",
Edit: "সম্পাদন করুন",
Create: "নতুন তৈরি করুন",
Edit: "সম্পাদন করুন",
Export: "রপ্তানি করুন",
Import: "আমদানি করুন",
Sync: "সিঙ্ক",
Config: "কনফিগারেশন",
},
Exporter: {
Description: {
Title: "শুধুমাত্র কনটেক্সট পরিষ্কার করার পরে বার্তাগুলি প্রদর্শিত হবে",
},
Model: "মডেল",
Messages: "বার্তা",
Topic: "টপিক",
Topic: "থিম",
Time: "সময়",
},
};

View File

@@ -42,6 +42,7 @@ const cn = {
PinToastAction: "查看",
Delete: "删除",
Edit: "编辑",
FullScreen: "全屏",
},
Commands: {
new: "新建聊天",
@@ -104,6 +105,10 @@ const cn = {
Toast: "正在生成截图",
Modal: "长按或右键保存图片",
},
Artifacts: {
Title: "分享页面",
Error: "分享失败",
},
},
Select: {
Search: "搜索消息",
@@ -128,6 +133,7 @@ const cn = {
Settings: {
Title: "设置",
SubTitle: "所有设置选项",
ShowPassword: "显示密码",
Danger: {
Reset: {
@@ -152,6 +158,11 @@ const cn = {
Title: "字体大小",
SubTitle: "聊天内容的字体大小",
},
FontFamily: {
Title: "聊天字体",
SubTitle: "聊天内容的字体,若置空则应用全局默认字体",
Placeholder: "字体名称",
},
InjectSystemPrompts: {
Title: "注入系统级提示信息",
SubTitle: "强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示",
@@ -346,6 +357,10 @@ const cn = {
Title: "API 版本(仅适用于 gemini-pro",
SubTitle: "选择一个特定的 API 版本",
},
GoogleSafetySettings: {
Title: "Google 安全过滤级别",
SubTitle: "设置内容过滤级别",
},
},
Baidu: {
ApiKey: {
@@ -363,6 +378,22 @@ const cn = {
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: "接口密钥",
@@ -385,6 +416,44 @@ const cn = {
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: {
Title: "自定义模型名",
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
@@ -440,12 +509,67 @@ const cn = {
Clear: "上下文已清除",
Revert: "恢复上下文",
},
Plugin: {
Name: "插件",
Discovery: {
Name: "发现",
},
FineTuned: {
Sysmessage: "你是一个助手",
},
SearchChat: {
Name: "搜索",
Page: {
Title: "搜索聊天记录",
Search: "输入搜索关键词",
NoResult: "没有找到结果",
NoData: "没有数据",
Loading: "加载中",
SubTitle: (count: number) => `搜索到 ${count} 条结果`,
},
Item: {
View: "查看",
},
},
Plugin: {
Name: "插件",
Page: {
Title: "插件",
SubTitle: (count: number) => `${count} 个插件`,
Search: "搜索插件",
Create: "新建",
Find: "您可以在Github上找到优秀的插件",
},
Item: {
Info: (count: number) => `${count} 方法`,
View: "查看",
Edit: "编辑",
Delete: "删除",
DeleteConfirm: "确认删除?",
},
Auth: {
None: "不需要授权",
Basic: "Basic",
Bearer: "Bearer",
Custom: "自定义",
CustomHeader: "自定义参数名称",
Token: "Token",
Proxy: "使用代理",
ProxyDescription: "使用代理解决 CORS 错误",
Location: "位置",
LocationHeader: "Header",
LocationQuery: "Query",
LocationBody: "Body",
},
EditModal: {
Title: (readonly: boolean) => `编辑插件 ${readonly ? "(只读)" : ""}`,
Download: "下载",
Auth: "授权方式",
Content: "OpenAPI Schema",
Load: "从网页加载",
Method: "方法",
Error: "格式错误",
},
},
Mask: {
Name: "面具",
Page: {
@@ -480,6 +604,10 @@ const cn = {
Title: "隐藏预设对话",
SubTitle: "隐藏后预设对话不会出现在聊天界面",
},
Artifacts: {
Title: "启用Artifacts",
SubTitle: "启用之后可以直接渲染HTML页面",
},
Share: {
Title: "分享此面具",
SubTitle: "生成此面具的直达链接",
@@ -522,6 +650,61 @@ const cn = {
Topic: "主题",
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

View File

@@ -1,233 +1,562 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const cs: PartialLocaleType = {
WIP: "V přípravě...",
Error: {
Unauthorized:
"Neoprávněný přístup, zadejte přístupový kód na [stránce](/#/auth) nastavení.",
Unauthorized: isApp
? "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."
: "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: {
ChatItemCount: (count: number) => `${count} zpráv`,
ChatItemCount: (count: number) => `${count} konverzací`,
},
Chat: {
SubTitle: (count: number) => `${count} zpráv s ChatGPT`,
SubTitle: (count: number) => `Celkem ${count} konverzací`,
EditMessage: {
Title: "Upravit zprávy",
Topic: {
Title: "Téma konverzace",
SubTitle: "Změnit aktuální téma konverzace",
},
},
Actions: {
ChatList: "Přejít na seznam chatů",
CompressedHistory: "Pokyn z komprimované paměti historie",
Export: "Exportovat všechny zprávy jako Markdown",
ChatList: "Zobrazit seznam zpráv",
CompressedHistory: "Zobrazit komprimovanou historii Prompt",
Export: "Exportovat konverzace",
Copy: "Kopírovat",
Stop: "Zastavit",
Retry: "Zopakovat",
Retry: "Zkusit znovu",
Pin: "Připnout",
PinToastContent: "1 konverzace byla připnuta k přednastaveným promptům",
PinToastAction: "Zobrazit",
Delete: "Smazat",
Edit: "Upravit",
},
Rename: "Přejmenovat chat",
Typing: "Píše...",
Commands: {
new: "Nová konverzace",
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) => {
var inputHints = `${submitKey} pro odeslání`;
var inputHints = `${submitKey} odeslat`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter pro řádkování";
inputHints += "Shift + Enter pro nový řádek";
}
return inputHints + ", / pro vyhledávání pokynů";
return inputHints + "/ pro doplnění, : pro příkaz";
},
Send: "Odeslat",
Config: {
Reset: "Obnovit výchozí",
SaveAs: "Uložit jako Masku",
Reset: "Vymazat paměť",
SaveAs: "Uložit jako masku",
},
IsContext: "Přednastavené prompty",
},
Export: {
Title: "Všechny zprávy",
Title: "Sdílet konverzace",
Copy: "Kopírovat vše",
Download: "Stáhnout",
MessageFromYou: "Zpráva od vás",
MessageFromChatGPT: "Zpráva z ChatGPT",
Download: "Stáhnout soubor",
Share: "Sdílet na ShareGPT",
MessageFromYou: "Uživatel",
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: {
Title: "Pokyn z paměti",
EmptyContent: "Zatím nic.",
Send: "Odeslat paměť",
Copy: "Kopírovat paměť",
Reset: "Obnovit relaci",
ResetConfirm:
"Resetováním se vymaže historie aktuálních konverzací i paměť historie pokynů. Opravdu chcete provést obnovu?",
Title: "Historie shrnutí",
EmptyContent: "Obsah konverzace je příliš krátký, není třeba shrnovat",
Send: "Automaticky komprimovat konverzace a odeslat jako kontext",
Copy: "Kopírovat shrnutí",
Reset: "[nepoužívá se]",
ResetConfirm: "Opravdu chcete vymazat historii shrnutí?",
},
Home: {
NewChat: "Nový chat",
DeleteChat: "Potvrzujete smazání vybrané konverzace?",
DeleteToast: "Chat smazán",
Revert: "Zvrátit",
NewChat: "Nová konverzace",
DeleteChat: "Opravdu chcete smazat vybranou konverzaci?",
DeleteToast: "Konverzace byla smazána",
Revert: "Vrátit",
},
Settings: {
Title: "Nastavení",
SubTitle: "Všechna nastavení",
SubTitle: "Všechny možnosti 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: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Language", // POZOR: pokud chcete přidat nový překlad, prosím, nechte tuto hodnotu jako `Language`
All: "Všechny jazyky",
},
Avatar: "Avatar",
Avatar: "Profilový obrázek",
FontSize: {
Title: "Velikost písma",
SubTitle: "Nastavení velikosti písma obsahu chatu",
SubTitle: "Velikost písma pro obsah 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: {
Title: "Vložit systémové prompty",
Title: "Vložit systémové výzvy",
SubTitle:
"Vynutit přidání simulovaného systémového promptu ChatGPT na začátek seznamu zpráv každého požadavku",
"Automaticky přidat systémovou výzvu simulující ChatGPT na začátek seznamu zpráv pro každý požadavek",
},
InputTemplate: {
Title: "Předzpracování uživatelského vstupu",
SubTitle: "Nejnovější zpráva uživatele bude vyplněna do této šablony",
},
Update: {
Version: (x: string) => `Verze: ${x}`,
IsLatest: "Aktuální verze",
Version: (x: string) => `Aktuální verze: ${x}`,
IsLatest: "Jste na nejnovější verzi",
CheckUpdate: "Zkontrolovat aktualizace",
IsChecking: "Kontrola aktualizace...",
IsChecking: "Kontrola aktualizací...",
FoundUpdate: (x: string) => `Nalezena nová verze: ${x}`,
GoToUpdate: "Aktualizovat",
GoToUpdate: "Přejít na aktualizaci",
},
SendKey: "Odeslat klíč",
SendKey: "Klávesa pro odeslání",
Theme: "Téma",
TightBorder: "Těsné ohraničení",
TightBorder: "Režim bez okra",
SendPreviewBubble: {
Title: "Odesílat chatovací bublinu s náhledem",
SubTitle: "Zobrazit v náhledu bubliny",
Title: "Náhledová bublina",
SubTitle: "Náhled Markdown obsahu v náhledové bublině",
},
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: {
Splash: {
Title: "Úvodní obrazovka Masek",
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
Title: "Úvodní stránka masky",
SubTitle: "Při zahájení nové konverzace zobrazit úvodní stránku masky",
},
Builtin: {
Title: "Skrýt vestavěné masky",
SubTitle: "Skrýt vestavěné masky v seznamu všech masek",
},
},
Prompt: {
Disable: {
Title: "Deaktivovat automatické dokončování",
SubTitle: "Zadejte / pro spuštění automatického dokončování",
Title: "Zakázat automatické doplňování promptů",
SubTitle:
"Automatické doplňování se aktivuje zadáním / na začátku textového pole",
},
List: "Seznam pokynů",
List: "Seznam vlastních promptů",
ListCount: (builtin: number, custom: number) =>
`${builtin} vestavěných, ${custom} uživatelských`,
`Vestavěné ${builtin} položek, uživatelsky definované ${custom} položek`,
Edit: "Upravit",
Modal: {
Title: "Seznam pokynů",
Add: "Přidat pokyn",
Search: "Hledat pokyny",
Title: "Seznam promptů",
Add: "Nový",
Search: "Hledat prompty",
},
EditModal: {
Title: "Editovat pokyn",
Title: "Upravit prompt",
},
},
HistoryCount: {
Title: "Počet připojených zpráv",
SubTitle: "Počet odeslaných připojených zpráv na žádost",
Title: "Počet historických zpráv",
SubTitle: "Počet historických zpráv zahrnutých v každém požadavku",
},
CompressThreshold: {
Title: "Práh pro kompresi historie",
Title: "Prahová hodnota komprese historických zpráv",
SubTitle:
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
"Když nekomprimované historické zprávy překročí tuto hodnotu, dojde ke kompresi",
},
Usage: {
Title: "Stav účtu",
Title: "Kontrola zůstatku",
SubTitle(used: any, total: any) {
return `Použito tento měsíc $${used}, předplaceno $${total}`;
return `Tento měsíc použito $${used}, celkový předplatný objem $${total}`;
},
IsChecking: "Kontroluji...",
Check: "Zkontrolovat",
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
IsChecking: "Probíhá kontrola…",
Check: "Znovu zkontrolovat",
NoAccess: "Zadejte API Key nebo přístupové heslo pro zobrazení zůstatku",
},
Model: "Model",
Access: {
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: {
Title: "Teplota",
SubTitle: "Větší hodnota činí výstup náhodnějším",
Title: "Náhodnost (temperature)",
SubTitle: "Čím vyšší hodnota, tím náhodnější odpovědi",
},
TopP: {
Title: "Jádrové vzorkování (top_p)",
SubTitle: "Podobné náhodnosti, ale neměňte spolu s náhodností",
},
MaxTokens: {
Title: "Max. počet tokenů",
SubTitle: "Maximální délka vstupního tokenu a generovaných tokenů",
Title: "Omezení odpovědi (max_tokens)",
SubTitle: "Maximální počet Tokenů použitých v jednom interakci",
},
PresencePenalty: {
Title: "Přítomnostní korekce",
SubTitle: "Větší hodnota zvyšuje pravděpodobnost nových témat.",
Title: "Čerstvost témat (presence_penalty)",
SubTitle:
"Čím vyšší hodnota, tím větší pravděpodobnost rozšíření na nová témata",
},
FrequencyPenalty: {
Title: "Frekvenční penalizace",
Title: "Penalizace frekvence (frequency_penalty)",
SubTitle:
"Větší hodnota snižující pravděpodobnost opakování stejného řádku",
"Čím vyšší hodnota, tím větší pravděpodobnost snížení opakování slov",
},
},
Store: {
DefaultTopic: "Nová konverzace",
BotHello: "Ahoj! Jak mohu dnes pomoci?",
Error: "Něco se pokazilo, zkuste to prosím později.",
DefaultTopic: "Nový chat",
BotHello: "Jak vám mohu pomoci?",
Error: "Došlo k chybě, zkuste to prosím znovu později.",
Prompt: {
History: (content: string) =>
"Toto je shrnutí historie chatu mezi umělou inteligencí a uživatelem v podobě rekapitulace: " +
content,
"Toto je shrnutí historie chatu jako kontext: " + content,
Topic:
"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.",
"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'.",
Summarize:
"Krátce shrň naši diskusi v rozsahu do 200 slov a použij ji jako podnět pro budoucí kontext.",
"Stručně shrňte obsah konverzace jako kontextový prompt pro budoucí použití, do 200 slov",
},
},
Copy: {
Success: "Zkopírováno do schránky",
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: {
Toast: (x: any) => `Použití ${x} kontextových pokynů`,
Edit: "Kontextové a paměťové pokyny",
Add: "Přidat pokyn",
Toast: (x: any) => `Obsahuje ${x} přednastavených promptů`,
Edit: "Nastavení aktuální konverzace",
Add: "Přidat novou konverzaci",
Clear: "Kontext byl vymazán",
Revert: "Obnovit kontext",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Jste asistent, který",
Sysmessage: "Jste asistent",
},
SearchChat: {
Name: "Hledat",
Page: {
Title: "Hledat v historii chatu",
Search: "Zadejte hledané klíčové slovo",
NoResult: "Nebyly nalezeny žádné výsledky",
NoData: "Žádná data",
Loading: "Načítání",
SubTitle: (count: number) => `Nalezeno ${count} výsledků`,
},
Item: {
View: "Zobrazit",
},
},
Mask: {
Name: "Maska",
Page: {
Title: "Šablona pokynu",
SubTitle: (count: number) => `${count} šablon pokynů`,
Search: "Hledat v šablonách",
Create: "Vytvořit",
Title: "Přednastavené role masky",
SubTitle: (count: number) => `${count} definovaných rolí`,
Search: "Hledat role masky",
Create: "Nový",
},
Item: {
Info: (count: number) => `${count} pokynů`,
Info: (count: number) => `Obsahuje ${count} přednastavených konverzací`,
Chat: "Chat",
View: "Zobrazit",
Edit: "Upravit",
Delete: "Smazat",
DeleteConfirm: "Potvrdit smazání?",
DeleteConfirm: "Opravdu chcete smazat?",
},
EditModal: {
Title: (readonly: boolean) =>
`Editovat šablonu pokynu ${readonly ? "(pouze ke čtení)" : ""}`,
Download: "Stáhnout",
Clone: "Duplikovat",
`Upravit přednastavenou masku ${readonly ? " (jen pro čtení)" : ""}`,
Download: "Stáhnout přednastavení",
Clone: "Klonovat přednastavení",
},
Config: {
Avatar: "Avatar Bota",
Name: "Jméno Bota",
Avatar: "Profilový obrázek",
Name: "Název role",
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: {
Return: "Zpět",
Skip: "Přeskočit",
Title: "Vyberte Masku",
SubTitle: "Chatovat s duší za Maskou",
More: "Najít více",
NotShow: "Nezobrazovat znovu",
ConfirmNoShow: "Potvrdit zakázáníMůžete jej povolit později v nastavení.",
Skip: "Začít hned",
NotShow: "Zobrazit už nikdy",
ConfirmNoShow:
"Opravdu chcete zakázat? Zakázání můžete kdykoli znovu povolit v nastavení.",
Title: "Vyberte masku",
SubTitle: "Začněte nyní a konfrontujte se s myslí za maskou",
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: {
Confirm: "Potvrdit",
Cancel: "Zrušit",
Close: "Zavřít",
Create: "Vytvořit",
Create: "Nový",
Edit: "Upravit",
Export: "Exportovat",
Import: "Importovat",
Sync: "Synchronizovat",
Config: "Konfigurovat",
},
Exporter: {
Description: {
Title: "Pouze zprávy po vymazání kontextu budou zobrazeny",
},
Model: "Model",
Messages: "Zprávy",
Topic: "Téma",

View File

@@ -1,235 +1,580 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const de: PartialLocaleType = {
WIP: "In Bearbeitung...",
Error: {
Unauthorized:
"Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der [Einstellungsseite](/#/auth) ein.",
Unauthorized: isApp
? "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."
: "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: {
ChatItemCount: (count: number) => `${count} Nachrichten`,
ChatItemCount: (count: number) => `${count} Gespräche`,
},
Chat: {
SubTitle: (count: number) => `${count} Nachrichten mit ChatGPT`,
Actions: {
ChatList: "Zur Chat-Liste gehen",
CompressedHistory: "Komprimierter Gedächtnis-Prompt",
Export: "Alle Nachrichten als Markdown exportieren",
Copy: "Kopieren",
Stop: "Stop",
Retry: "Wiederholen",
Delete: "Delete",
SubTitle: (count: number) => `Insgesamt ${count} Gespräche`,
EditMessage: {
Title: "Nachricht bearbeiten",
Topic: {
Title: "Chat-Thema",
SubTitle: "Ändern Sie das aktuelle Chat-Thema",
},
},
Rename: "Chat umbenennen",
Typing: "Tippen...",
Actions: {
ChatList: "Nachrichtliste anzeigen",
CompressedHistory: "Komprimierte Historie anzeigen",
Export: "Chatverlauf exportieren",
Copy: "Kopieren",
Stop: "Stoppen",
Retry: "Erneut versuchen",
Pin: "Anheften",
PinToastContent: "1 Gespräch an den voreingestellten Prompt angeheftet",
PinToastAction: "Ansehen",
Delete: "Löschen",
Edit: "Bearbeiten",
},
Commands: {
new: "Neues Gespräch",
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) => {
var inputHints = `${submitKey} um zu Senden`;
var inputHints = `${submitKey} senden`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Umschalt + Eingabe für Zeilenumbruch";
inputHints += "Shift + Enter für Zeilenumbruch";
}
return inputHints + ", / zum Durchsuchen von Prompts";
return inputHints + "/ für Autovervollständigung, : für Befehle";
},
Send: "Senden",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
Reset: "Erinnerung löschen",
SaveAs: "Als Maske speichern",
},
IsContext: "Voreingestellter Prompt",
},
Export: {
Title: "Alle Nachrichten",
Title: "Chatverlauf teilen",
Copy: "Alles kopieren",
Download: "Herunterladen",
MessageFromYou: "Deine Nachricht",
MessageFromChatGPT: "Nachricht von ChatGPT",
Download: "Datei herunterladen",
Share: "Auf ShareGPT teilen",
MessageFromYou: "Benutzer",
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: {
Title: "Gedächtnis-Prompt",
EmptyContent: "Noch nichts.",
Send: "Gedächtnis senden",
Copy: "Gedächtnis kopieren",
Reset: "Sitzung zurücksetzen",
ResetConfirm:
"Das Zurücksetzen löscht den aktuellen Gesprächsverlauf und das Langzeit-Gedächtnis. Möchten Sie wirklich zurücksetzen?",
Title: "Historische Zusammenfassung",
EmptyContent:
"Gesprächsinhalte sind zu kurz, keine Zusammenfassung erforderlich",
Send: "Chatverlauf automatisch komprimieren und als Kontext senden",
Copy: "Zusammenfassung kopieren",
Reset: "[nicht verwendet]",
ResetConfirm: "Zusammenfassung löschen bestätigen?",
},
Home: {
NewChat: "Neuer Chat",
DeleteChat: "Bestätigen Sie, um das ausgewählte Gespräch zu löschen?",
DeleteToast: "Chat gelöscht",
Revert: "Zurücksetzen",
NewChat: "Neues Gespräch",
DeleteChat: "Bestätigen Sie das Löschen des ausgewählten Gesprächs?",
DeleteToast: "Gespräch gelöscht",
Revert: "Rückgängig machen",
},
Settings: {
Title: "Einstellungen",
SubTitle: "Alle Einstellungen",
SubTitle: "Alle Einstellungsmöglichkeiten",
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: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Sprache", // ACHTUNG: Wenn Sie eine neue Übersetzung hinzufügen möchten, übersetzen Sie diesen Wert bitte nicht, lassen Sie ihn als `Sprache`
All: "Alle Sprachen",
},
Avatar: "Avatar",
FontSize: {
Title: "Schriftgröße",
SubTitle: "Schriftgröße des Chat-Inhalts anpassen",
SubTitle: "Schriftgröße des Chat-Inhalts",
},
FontFamily: {
Title: "Chat-Schriftart",
SubTitle:
"Schriftart des Chat-Inhalts, leer lassen, um die globale Standardschriftart anzuwenden",
Placeholder: "Schriftartname",
},
InjectSystemPrompts: {
Title: "System-Prompts einfügen",
Title: "Systemweite Eingabeaufforderungen einfügen",
SubTitle:
"Erzwingt das Hinzufügen eines simulierten systemweiten Prompts von ChatGPT am Anfang der Nachrichtenliste bei jeder Anfrage",
"Fügt jeder Nachricht am Anfang der Nachrichtenliste eine simulierte ChatGPT-Systemaufforderung hinzu",
},
InputTemplate: {
Title: "Benutzer-Eingabeverarbeitung",
SubTitle:
"Die neueste Nachricht des Benutzers wird in diese Vorlage eingefügt",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Neueste Version",
CheckUpdate: "Update prüfen",
IsChecking: "Update wird geprüft...",
Version: (x: string) => `Aktuelle Version: ${x}`,
IsLatest: "Bereits die neueste Version",
CheckUpdate: "Auf Updates überprüfen",
IsChecking: "Überprüfe auf Updates...",
FoundUpdate: (x: string) => `Neue Version gefunden: ${x}`,
GoToUpdate: "Aktualisieren",
GoToUpdate: "Zum Update gehen",
},
SendKey: "Senden-Taste",
Theme: "Erscheinungsbild",
TightBorder: "Enger Rahmen",
SendKey: "Sende-Taste",
Theme: "Thema",
TightBorder: "Randloser Modus",
SendPreviewBubble: {
Title: "Vorschau-Bubble senden",
SubTitle: "Preview markdown in bubble",
Title: "Vorschau-Bubble",
SubTitle: "Markdown-Inhalt in der Vorschau-Bubble anzeigen",
},
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: {
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Title: "Masken-Startseite",
SubTitle:
"Zeige die Masken-Startseite beim Erstellen eines neuen Chats",
},
Builtin: {
Title: "Eingebaute Masken ausblenden",
SubTitle: "Blendet eingebaute Masken in allen Maskenlisten aus",
},
},
Prompt: {
Disable: {
Title: "Autovervollständigung deaktivieren",
SubTitle: "Autovervollständigung mit / starten",
Title: "Automatische Eingabeaufforderung deaktivieren",
SubTitle:
"Geben Sie am Anfang des Eingabefelds / ein, um die automatische Vervollständigung auszulösen",
},
List: "Prompt-Liste",
List: "Benutzerdefinierte Eingabeaufforderungsliste",
ListCount: (builtin: number, custom: number) =>
`${builtin} integriert, ${custom} benutzerdefiniert`,
`Eingebaut ${builtin} Stück, Benutzerdefiniert ${custom} Stück`,
Edit: "Bearbeiten",
Modal: {
Title: "Prompt List",
Add: "Add One",
Search: "Search Prompts",
Title: "Eingabeaufforderungsliste",
Add: "Neu erstellen",
Search: "Eingabeaufforderungen suchen",
},
EditModal: {
Title: "Edit Prompt",
Title: "Eingabeaufforderung bearbeiten",
},
},
HistoryCount: {
Title: "Anzahl der angehängten Nachrichten",
SubTitle: "Anzahl der pro Anfrage angehängten gesendeten Nachrichten",
Title: "Anzahl der historischen Nachrichten",
SubTitle:
"Anzahl der historischen Nachrichten, die bei jeder Anfrage mitgesendet werden",
},
CompressThreshold: {
Title: "Schwellenwert für Verlaufskomprimierung",
Title: "Komprimierungsschwelle für historische Nachrichtenlänge",
SubTitle:
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
"Wenn die unkomprimierten historischen Nachrichten diesen Wert überschreiten, wird komprimiert",
},
Usage: {
Title: "Kontostand",
Title: "Guthabenabfrage",
SubTitle(used: any, total: any) {
return `Diesen Monat ausgegeben $${used}, Abonnement $${total}`;
return `In diesem Monat verwendet $${used}, Abonnement insgesamt $${total}`;
},
IsChecking: "Wird überprüft...",
Check: "Erneut prüfen",
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
IsChecking: "Wird überprüft",
Check: "Erneut überprüfen",
NoAccess:
"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",
Temperature: {
Title: "Temperature", //Temperatur
SubTitle: "Ein größerer Wert führt zu zufälligeren Antworten",
Title: "Zufälligkeit (temperature)",
SubTitle: "Je höher der Wert, desto zufälliger die Antwort",
},
TopP: {
Title: "Kern-Sampling (top_p)",
SubTitle:
"Ähnlich der Zufälligkeit, aber nicht zusammen mit Zufälligkeit ändern",
},
MaxTokens: {
Title: "Max Tokens", //Maximale Token
SubTitle: "Maximale Anzahl der Anfrage- plus Antwort-Token",
Title: "Maximale Token-Anzahl pro Antwort",
SubTitle: "Maximale Anzahl der Tokens pro Interaktion",
},
PresencePenalty: {
Title: "Presence Penalty", //Anwesenheitsstrafe
Title: "Themenfrische (presence_penalty)",
SubTitle:
"Ein größerer Wert erhöht die Wahrscheinlichkeit, dass über neue Themen gesprochen wird",
"Je höher der Wert, desto wahrscheinlicher wird auf neue Themen eingegangen",
},
FrequencyPenalty: {
Title: "Frequency Penalty", // HäufigkeitStrafe
Title: "Häufigkeitsstrafe (frequency_penalty)",
SubTitle:
"Ein größerer Wert, der die Wahrscheinlichkeit verringert, dass dieselbe Zeile wiederholt wird",
"Je höher der Wert, desto wahrscheinlicher werden wiederholte Wörter reduziert",
},
},
Store: {
DefaultTopic: "Neues Gespräch",
BotHello: "Hallo! Wie kann ich Ihnen heute helfen?",
DefaultTopic: "Neuer Chat",
BotHello: "Wie kann ich Ihnen helfen?",
Error:
"Etwas ist schief gelaufen, bitte versuchen Sie es später noch einmal.",
"Ein Fehler ist aufgetreten, bitte versuchen Sie es später noch einmal",
Prompt: {
History: (content: string) =>
"Dies ist eine Zusammenfassung des Chatverlaufs zwischen dem KI und dem Benutzer als Rückblick: " +
"Dies ist eine Zusammenfassung des bisherigen Chats als Hintergrundinformation: " +
content,
Topic:
"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.",
"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.",
Summarize:
"Fassen Sie unsere Diskussion kurz in 200 Wörtern oder weniger zusammen, um sie als Pronpt für zukünftige Gespräche zu verwenden.",
"Fassen Sie den Gesprächsinhalt zusammen, um als Kontextaufforderung für den nächsten Schritt zu dienen, halten Sie es unter 200 Zeichen",
},
},
Copy: {
Success: "In die Zwischenablage kopiert",
Success: "In die Zwischenablage geschrieben",
Failed:
"Kopieren fehlgeschlagen, bitte geben Sie die Berechtigung zum Zugriff auf die Zwischenablage frei",
"Kopieren fehlgeschlagen, bitte erlauben Sie Zugriff auf die Zwischenablage",
},
Download: {
Success: "Inhalt wurde in Ihrem Verzeichnis heruntergeladen.",
Failed: "Download fehlgeschlagen.",
},
Context: {
Toast: (x: any) => `Mit ${x} Kontext-Prompts`,
Edit: "Kontext- und Gedächtnis-Prompts",
Add: "Hinzufügen",
Toast: (x: any) => `Beinhaltet ${x} vordefinierte Eingabeaufforderungen`,
Edit: "Aktuelle Gesprächseinstellungen",
Add: "Neues Gespräch hinzufügen",
Clear: "Kontext gelöscht",
Revert: "Kontext wiederherstellen",
},
Plugin: {
Name: "Plugin",
Name: "Plugins",
},
FineTuned: {
Sysmessage: "Du bist ein Assistent, der",
Sysmessage: "Du bist ein Assistent",
},
Mask: {
Name: "Mask",
SearchChat: {
Name: "Suche",
Page: {
Title: "Prompt Template",
SubTitle: (count: number) => `${count} prompt templates`,
Search: "Search Templates",
Create: "Create",
Title: "Chatverlauf durchsuchen",
Search: "Suchbegriff eingeben",
NoResult: "Keine Ergebnisse gefunden",
NoData: "Keine Daten",
Loading: "Laden",
SubTitle: (count: number) => `${count} Ergebnisse gefunden`,
},
Item: {
Info: (count: number) => `${count} prompts`,
Chat: "Chat",
View: "View",
Edit: "Edit",
Delete: "Delete",
DeleteConfirm: "Confirm to delete?",
View: "Ansehen",
},
},
Mask: {
Name: "Masken",
Page: {
Title: "Vordefinierte Rollenmasken",
SubTitle: (count: number) =>
`${count} vordefinierte Rollenbeschreibungen`,
Search: "Rollenmasken suchen",
Create: "Neu erstellen",
},
Item: {
Info: (count: number) => `Beinhaltet ${count} vordefinierte Gespräche`,
Chat: "Gespräch",
View: "Anzeigen",
Edit: "Bearbeiten",
Delete: "Löschen",
DeleteConfirm: "Bestätigen Sie das Löschen?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Clone: "Clone",
`Vordefinierte Maske bearbeiten ${readonly ? "Nur lesen" : ""}`,
Download: "Vorgabe herunterladen",
Clone: "Vorgabe klonen",
},
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Avatar: "Rollen-Avatar",
Name: "Rollenname",
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: {
Return: "Return",
Skip: "Skip",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Not Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
Return: "Zurück",
Skip: "Direkt beginnen",
NotShow: "Nicht mehr anzeigen",
ConfirmNoShow:
"Bestätigen Sie die Deaktivierung? Nach der Deaktivierung können Sie jederzeit in den Einstellungen wieder aktivieren.",
Title: "Wählen Sie eine Maske aus",
SubTitle:
"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: {
Confirm: "Confirm",
Cancel: "Cancel",
Close: "Close",
Create: "Create",
Edit: "Edit",
Confirm: "Bestätigen",
Cancel: "Abbrechen",
Close: "Schließen",
Create: "Neu erstellen",
Edit: "Bearbeiten",
Export: "Exportieren",
Import: "Importieren",
Sync: "Synchronisieren",
Config: "Konfigurieren",
},
Exporter: {
Description: {
Title: "Nur Nachrichten nach dem Löschen des Kontexts werden angezeigt",
},
Model: "Modell",
Messages: "Nachrichten",
Topic: "Thema",

View File

@@ -44,6 +44,7 @@ const en: LocaleType = {
PinToastAction: "View",
Delete: "Delete",
Edit: "Edit",
FullScreen: "FullScreen",
},
Commands: {
new: "Start a new chat",
@@ -106,6 +107,10 @@ const en: LocaleType = {
Toast: "Capturing Image...",
Modal: "Long press or right click to save image",
},
Artifacts: {
Title: "Share Artifacts",
Error: "Share Error",
},
},
Select: {
Search: "Search",
@@ -131,6 +136,7 @@ const en: LocaleType = {
Settings: {
Title: "Settings",
SubTitle: "All Settings",
ShowPassword: "ShowPassword",
Danger: {
Reset: {
Title: "Reset All Settings",
@@ -154,6 +160,12 @@ const en: LocaleType = {
Title: "Font Size",
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: {
Title: "Inject System Prompts",
SubTitle: "Inject a global system prompt for every request",
@@ -350,6 +362,22 @@ const en: LocaleType = {
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",
@@ -372,6 +400,44 @@ const en: LocaleType = {
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: {
Title: "Custom Models",
SubTitle: "Custom model options, seperated by comma",
@@ -392,6 +458,10 @@ const en: LocaleType = {
Title: "API Version (specific to gemini-pro)",
SubTitle: "Select a specific API version",
},
GoogleSafetySettings: {
Title: "Google Safety Settings",
SubTitle: "Select a safety filtering level",
},
},
},
@@ -447,12 +517,68 @@ const en: LocaleType = {
Clear: "Context Cleared",
Revert: "Revert",
},
Plugin: {
Name: "Plugin",
Discovery: {
Name: "Discovery",
},
FineTuned: {
Sysmessage: "You are an assistant that",
},
SearchChat: {
Name: "Search",
Page: {
Title: "Search Chat History",
Search: "Enter search query to search chat history",
NoResult: "No results found",
NoData: "No data",
Loading: "Loading...",
SubTitle: (count: number) => `Found ${count} results`,
},
Item: {
View: "View",
},
},
Plugin: {
Name: "Plugin",
Page: {
Title: "Plugins",
SubTitle: (count: number) => `${count} plugins`,
Search: "Search Plugin",
Create: "Create",
Find: "You can find awesome plugins on github: ",
},
Item: {
Info: (count: number) => `${count} method`,
View: "View",
Edit: "Edit",
Delete: "Delete",
DeleteConfirm: "Confirm to delete?",
},
Auth: {
None: "None",
Basic: "Basic",
Bearer: "Bearer",
Custom: "Custom",
CustomHeader: "Parameter Name",
Token: "Token",
Proxy: "Using Proxy",
ProxyDescription: "Using proxies to solve CORS error",
Location: "Location",
LocationHeader: "Header",
LocationQuery: "Query",
LocationBody: "Body",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Plugin ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Auth: "Authentication Type",
Content: "OpenAPI Schema",
Load: "Load From URL",
Method: "Method",
Error: "OpenAPI Schema Error",
},
},
Mask: {
Name: "Mask",
Page: {
@@ -487,6 +613,10 @@ const en: LocaleType = {
Title: "Hide Context Prompts",
SubTitle: "Do not show in-context prompts in chat",
},
Artifacts: {
Title: "Enable Artifacts",
SubTitle: "Can render HTML page when enable artifacts.",
},
Share: {
Title: "Share This Mask",
SubTitle: "Generate a link to this mask",
@@ -524,11 +654,65 @@ const en: LocaleType = {
Topic: "Topic",
Time: "Time",
},
URLCommand: {
Code: "Detected access code 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;

View File

@@ -1,239 +1,580 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const es: PartialLocaleType = {
WIP: "En construcción...",
Error: {
Unauthorized:
"Acceso no autorizado, por favor ingrese el código de acceso en la [página](/#/auth) de configuración.",
Unauthorized: isApp
? "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."
: "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: {
ChatItemCount: (count: number) => `${count} mensajes`,
ChatItemCount: (count: number) => `${count} conversaciones`,
},
Chat: {
SubTitle: (count: number) => `${count} mensajes con ChatGPT`,
SubTitle: (count: number) => `Total de ${count} conversaciones`,
EditMessage: {
Title: "Editar registro de mensajes",
Topic: {
Title: "Tema de la conversación",
SubTitle: "Cambiar el tema de la conversación actual",
},
},
Actions: {
ChatList: "Ir a la lista de chats",
CompressedHistory: "Historial de memoria comprimido",
Export: "Exportar todos los mensajes como Markdown",
ChatList: "Ver lista de mensajes",
CompressedHistory: "Ver historial de Prompts comprimidos",
Export: "Exportar historial de chat",
Copy: "Copiar",
Stop: "Detener",
Retry: "Reintentar",
Delete: "Delete",
Pin: "Fijar",
PinToastContent:
"Se ha fijado 1 conversación a los prompts predeterminados",
PinToastAction: "Ver",
Delete: "Eliminar",
Edit: "Editar",
},
Rename: "Renombrar chat",
Typing: "Escribiendo...",
Commands: {
new: "Nueva conversación",
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) => {
var inputHints = `Escribe algo y presiona ${submitKey} para enviar`;
var inputHints = `${submitKey} para enviar`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", presiona Shift + Enter para nueva línea";
inputHints += "Shift + Enter para nueva línea";
}
return inputHints;
return (
inputHints + "/ para activar autocompletado: para activar comandos"
);
},
Send: "Enviar",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
Reset: "Borrar memoria",
SaveAs: "Guardar como máscara",
},
IsContext: "Prompt predeterminado",
},
Export: {
Title: "Todos los mensajes",
Title: "Compartir historial de chat",
Copy: "Copiar todo",
Download: "Descargar",
MessageFromYou: "Mensaje de ti",
MessageFromChatGPT: "Mensaje de ChatGPT",
Download: "Descargar archivo",
Share: "Compartir en ShareGPT",
MessageFromYou: "Usuario",
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: {
Title: "Historial de memoria",
EmptyContent: "Aún no hay nada.",
Copy: "Copiar todo",
Send: "Send Memory",
Reset: "Reset Session",
ResetConfirm:
"Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?",
Title: "Resumen histórico",
EmptyContent:
"El contenido de la conversación es demasiado corto para resumir",
Send: "Comprimir automáticamente el historial de chat y enviarlo como contexto",
Copy: "Copiar resumen",
Reset: "[no usado]",
ResetConfirm: "¿Confirmar para borrar el resumen histórico?",
},
Home: {
NewChat: "Nuevo chat",
DeleteChat: "¿Confirmar eliminación de la conversación seleccionada?",
DeleteToast: "Chat Deleted",
Revert: "Revert",
NewChat: "Nueva conversación",
DeleteChat: "¿Confirmar la eliminación de la conversación seleccionada?",
DeleteToast: "Conversación eliminada",
Revert: "Deshacer",
},
Settings: {
Title: "Configuración",
SubTitle: "Todas las configuraciones",
SubTitle: "Todas las opciones de configuración",
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: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Language", // ATENCIÓN: si deseas agregar una nueva traducción, por favor no traduzcas este valor, déjalo como `Language`
All: "Todos los idiomas",
},
Avatar: "Avatar",
FontSize: {
Title: "Tamaño de fuente",
SubTitle: "Ajustar el tamaño de fuente del contenido del chat",
SubTitle: "Tamaño de la 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: {
Title: "Inyectar Prompts del Sistema",
Title: "Inyectar mensajes del sistema",
SubTitle:
"Agregar forzosamente un prompt de sistema simulado de ChatGPT al comienzo de la lista de mensajes en cada solicitud",
"Forzar la adición de un mensaje del sistema simulado de ChatGPT al principio de cada lista de mensajes",
},
InputTemplate: {
Title: "Preprocesamiento de entrada del usuario",
SubTitle: "El último mensaje del usuario se rellenará en esta plantilla",
},
Update: {
Version: (x: string) => `Versión: ${x}`,
IsLatest: "Última versión",
Version: (x: string) => `Versión actual: ${x}`,
IsLatest: "Ya estás en la última versión",
CheckUpdate: "Buscar actualizaciones",
IsChecking: "Buscando actualizaciones...",
FoundUpdate: (x: string) => `Se encontró una nueva versión: ${x}`,
GoToUpdate: "Actualizar",
FoundUpdate: (x: string) => `Nueva versión encontrada: ${x}`,
GoToUpdate: "Ir a actualizar",
},
SendKey: "Tecla de envío",
SendKey: "Tecla de enviar",
Theme: "Tema",
TightBorder: "Borde ajustado",
TightBorder: "Modo sin borde",
SendPreviewBubble: {
Title: "Enviar burbuja de vista previa",
SubTitle: "Preview markdown in bubble",
Title: "Vista previa del globo",
SubTitle:
"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: {
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Title: "Pantalla de inicio de máscara",
SubTitle:
"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: {
Disable: {
Title: "Desactivar autocompletado",
SubTitle: "Escribe / para activar el autocompletado",
Title: "Deshabilitar autocompletado de prompts",
SubTitle:
"Escribe / al principio del campo de entrada para activar el autocompletado",
},
List: "Lista de autocompletado",
List: "Lista de prompts personalizados",
ListCount: (builtin: number, custom: number) =>
`${builtin} incorporado, ${custom} definido por el usuario`,
`Integrados ${builtin}, definidos por el usuario ${custom}`,
Edit: "Editar",
Modal: {
Title: "Prompt List",
Add: "Add One",
Search: "Search Prompts",
Title: "Lista de prompts",
Add: "Nuevo",
Search: "Buscar prompts",
},
EditModal: {
Title: "Edit Prompt",
Title: "Editar prompt",
},
},
HistoryCount: {
Title: "Cantidad de mensajes adjuntos",
SubTitle: "Número de mensajes enviados adjuntos por solicitud",
Title: "Número de mensajes históricos adjuntos",
SubTitle: "Número de mensajes históricos enviados con cada solicitud",
},
CompressThreshold: {
Title: "Umbral de compresión de historial",
Title: "Umbral de compresión de mensajes históricos",
SubTitle:
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
"Cuando los mensajes históricos no comprimidos superan este valor, se realizará la compresión",
},
Usage: {
Title: "Saldo de la cuenta",
Title: "Consulta de saldo",
SubTitle(used: any, total: any) {
return `Usado $${used}, subscription $${total}`;
return `Saldo usado este mes: $${used}, total suscrito: $${total}`;
},
IsChecking: "Comprobando...",
Check: "Comprobar de nuevo",
NoAccess: "Introduzca la clave API para comprobar el saldo",
IsChecking: "Verificando",
Check: "Revisar de nuevo",
NoAccess:
"Introduce la clave API o la contraseña de acceso para ver el saldo",
},
Model: "Modelo",
Access: {
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: {
Title: "Temperatura",
SubTitle: "Un valor mayor genera una salida más aleatoria",
Title: "Aleatoriedad (temperature)",
SubTitle: "Cuanto mayor sea el valor, más aleatorio será el resultado",
},
TopP: {
Title: "Muestreo por núcleo (top_p)",
SubTitle: "Similar a la aleatoriedad, pero no cambies ambos a la vez",
},
MaxTokens: {
Title: "Máximo de tokens",
SubTitle: "Longitud máxima de tokens de entrada y tokens generados",
Title: "Límite de tokens por respuesta (max_tokens)",
SubTitle: "Número máximo de tokens utilizados en una sola interacción",
},
PresencePenalty: {
Title: "Penalización de presencia",
Title: "Novedad de temas (presence_penalty)",
SubTitle:
"Un valor mayor aumenta la probabilidad de hablar sobre nuevos temas",
"Cuanto mayor sea el valor, más probable es que se amplíen a nuevos temas",
},
FrequencyPenalty: {
Title: "Penalización de frecuencia",
Title: "Penalización de frecuencia (frequency_penalty)",
SubTitle:
"Un valor mayor que disminuye la probabilidad de repetir la misma línea",
"Cuanto mayor sea el valor, más probable es que se reduzcan las palabras repetidas",
},
},
Store: {
DefaultTopic: "Nueva conversación",
BotHello: "¡Hola! ¿Cómo puedo ayudarte hoy?",
Error: "Algo salió mal, por favor intenta nuevamente más tarde.",
DefaultTopic: "Nuevo chat",
BotHello: "¿En qué puedo ayudarte?",
Error: "Hubo un error, inténtalo de nuevo más tarde",
Prompt: {
History: (content: string) =>
"Este es un resumen del historial del chat entre la IA y el usuario como recapitulación: " +
content,
"Este es un resumen del chat histórico como referencia: " + content,
Topic:
"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.",
"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'",
Summarize:
"Resuma nuestra discusión brevemente en 200 caracteres o menos para usarlo como un recordatorio para futuros contextos.",
"Resume brevemente el contenido de la conversación para usar como un prompt de contexto, manteniéndolo dentro de 200 palabras",
},
},
Copy: {
Success: "Copiado al portapapeles",
Failed:
"La copia falló, por favor concede permiso para acceder al portapapeles",
Failed: "Error al copiar, por favor otorga permisos al portapapeles",
},
Download: {
Success: "Contenido descargado en tu directorio.",
Failed: "Error al descargar.",
},
Context: {
Toast: (x: any) => `With ${x} contextual prompts`,
Edit: "Contextual and Memory Prompts",
Add: "Add One",
Toast: (x: any) => `Contiene ${x} prompts predefinidos`,
Edit: "Configuración del chat actual",
Add: "Agregar una conversación",
Clear: "Contexto borrado",
Revert: "Restaurar contexto",
},
Plugin: {
Name: "Plugin",
Name: "Complemento",
},
FineTuned: {
Sysmessage: "Eres un asistente que",
Sysmessage: "Eres un asistente",
},
Mask: {
Name: "Mask",
SearchChat: {
Name: "Buscar",
Page: {
Title: "Prompt Template",
SubTitle: (count: number) => `${count} prompt templates`,
Search: "Search Templates",
Create: "Create",
Title: "Buscar en el historial de chat",
Search: "Ingrese la palabra clave de búsqueda",
NoResult: "No se encontraron resultados",
NoData: "Sin datos",
Loading: "Cargando",
SubTitle: (count: number) => `Se encontraron ${count} resultados`,
},
Item: {
Info: (count: number) => `${count} prompts`,
View: "Ver",
},
},
Mask: {
Name: "Máscara",
Page: {
Title: "Máscaras de rol predefinidas",
SubTitle: (count: number) => `${count} definiciones de rol predefinidas`,
Search: "Buscar máscara de rol",
Create: "Crear nuevo",
},
Item: {
Info: (count: number) => `Contiene ${count} conversaciones predefinidas`,
Chat: "Chat",
View: "View",
Edit: "Edit",
Delete: "Delete",
DeleteConfirm: "Confirm to delete?",
View: "Ver",
Edit: "Editar",
Delete: "Eliminar",
DeleteConfirm: "¿Confirmar eliminación?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Clone: "Clone",
`Editar máscara predefinida ${readonly ? "solo lectura" : ""}`,
Download: "Descargar predefinido",
Clone: "Clonar predefinido",
},
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Avatar: "Avatar del rol",
Name: "Nombre del rol",
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: {
Return: "Return",
Skip: "Skip",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Not Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
Return: "Regresar",
Skip: "Comenzar ahora",
NotShow: "No mostrar más",
ConfirmNoShow:
"¿Confirmar desactivación? Puedes reactivar en la configuración en cualquier momento.",
Title: "Selecciona una máscara",
SubTitle: "Comienza ahora y colisiona con la mente detrás de la máscara",
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: {
Confirm: "Confirm",
Cancel: "Cancel",
Close: "Close",
Create: "Create",
Edit: "Edit",
Confirm: "Confirmar",
Cancel: "Cancelar",
Close: "Cerrar",
Create: "Crear",
Edit: "Editar",
Export: "Exportar",
Import: "Importar",
Sync: "Sincronizar",
Config: "Configurar",
},
Exporter: {
Description: {
Title: "Solo se mostrarán los mensajes después de borrar el contexto",
},
Model: "Modelo",
Messages: "Mensajes",
Topic: "Tema",
Time: "Time",
Time: "Hora",
},
};

View File

@@ -1,294 +1,559 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const fr: PartialLocaleType = {
WIP: "Prochainement...",
Error: {
Unauthorized:
"Accès non autorisé, veuillez saisir le code d'accès dans la [page](/#/auth) des paramètres.",
Unauthorized: isApp
? "Clé API invalide détectée. Veuillez vérifier si la clé API est correctement configurée dans la page [Paramètres](/#/settings)."
: "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: {
ChatItemCount: (count: number) => `${count} messages en total`,
ChatItemCount: (count: number) => `${count} conversations`,
},
Chat: {
SubTitle: (count: number) => `${count} messages échangés avec ChatGPT`,
SubTitle: (count: number) => `Total de ${count} conversations`,
EditMessage: {
Title: "Modifier l'historique des messages",
Topic: {
Title: "Sujet de la discussion",
SubTitle: "Modifier le sujet de la discussion actuel",
},
},
Actions: {
ChatList: "Aller à la liste de discussion",
CompressedHistory: "Mémoire d'historique compressée Prompt",
Export: "Exporter tous les messages en tant que Markdown",
ChatList: "Voir la liste des messages",
CompressedHistory: "Voir l'historique des prompts compressés",
Export: "Exporter l'historique de la discussion",
Copy: "Copier",
Stop: "Arrêter",
Retry: "Réessayer",
Delete: "Supprimer",
Pin: "Épingler",
PinToastContent: "Épingler 2 messages à des messages contextuels",
PinToastContent: "1 conversation épinglée aux prompts prédéfinis",
PinToastAction: "Voir",
Delete: "Supprimer",
Edit: "Modifier",
},
Commands: {
new: "Commencer une nouvelle conversation",
newm: "Démarrer une nouvelle conversation avec un assistant",
next: "Conversation suivante",
prev: "Conversation précédente",
new: "Nouvelle discussion",
newm: "Créer une discussion à partir du masque",
next: "Discussion suivante",
prev: "Discussion précédente",
clear: "Effacer le contexte",
del: "Supprimer la Conversation",
del: "Supprimer la discussion",
},
InputActions: {
Stop: "Stop",
ToBottom: "Au dernier",
Stop: "Arrêter la réponse",
ToBottom: "Aller au plus récent",
Theme: {
auto: "Auto",
light: "Thème clair",
dark: "Thème sombre",
auto: "Thème automatique",
light: "Mode clair",
dark: "Mode sombre",
},
Prompt: "Instructions",
Masks: "Assistants",
Clear: "Effacer le contexte",
Settings: "Réglages",
Prompt: "Commandes rapides",
Masks: "Tous les masques",
Clear: "Effacer la discussion",
Settings: "Paramètres de la discussion",
UploadImage: "Télécharger une image",
},
Rename: "Renommer la conversation",
Rename: "Renommer la discussion",
Typing: "En train d'écrire…",
Input: (submitKey: string) => {
var inputHints = `Appuyez sur ${submitKey} pour envoyer`;
var inputHints = `${submitKey} pour envoyer`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter pour insérer un saut de ligne";
inputHints += "Shift + Enter pour passer à la ligne";
}
return inputHints + ", / pour rechercher des prompts";
return inputHints + "/ pour compléter, : pour déclencher des commandes";
},
Send: "Envoyer",
Config: {
Reset: "Restaurer les paramètres par défaut",
SaveAs: "Enregistrer en tant que masque",
Reset: "Effacer la mémoire",
SaveAs: "Enregistrer comme masque",
},
IsContext: "Prompt prédéfini",
},
Export: {
Title: "Tous les messages",
Copy: "Tout sélectionner",
Download: "Télécharger",
MessageFromYou: "Message de votre part",
MessageFromChatGPT: "Message de ChatGPT",
Title: "Partager l'historique des discussions",
Copy: "Tout copier",
Download: "Télécharger le fichier",
Share: "Partager sur ShareGPT",
MessageFromYou: "Utilisateur",
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: {
Title: "Prompt mémoire",
EmptyContent: "Rien encore.",
Send: "Envoyer la mémoire",
Copy: "Copier la mémoire",
Reset: "Réinitialiser la session",
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?",
Title: "Résumé historique",
EmptyContent: "Le contenu de la discussion est trop court pour être résumé",
Send: "Compresser automatiquement l'historique des discussions et l'envoyer comme contexte",
Copy: "Copier le résumé",
Reset: "[unused]",
ResetConfirm: "Confirmer la suppression du résumé historique ?",
},
Home: {
NewChat: "Nouvelle discussion",
DeleteChat: "Confirmer la suppression de la conversation sélectionnée ?",
DeleteToast: "Conversation supprimée",
Revert: "Revenir en arrière",
DeleteChat: "Confirmer la suppression de la discussion sélectionnée ?",
DeleteToast: "Discussion supprimée",
Revert: "Annuler",
},
Settings: {
Title: "Paramètres",
SubTitle: "Toutes les configurations",
SubTitle: "Toutes les options de configuration",
Danger: {
Reset: {
Title: "Restaurer les paramètres",
SubTitle: "Restaurer les paramètres par défaut",
Action: "Reinitialiser",
Confirm: "Confirmer la réinitialisation des paramètres?",
Title: "Réinitialiser tous les paramètres",
SubTitle:
"Réinitialiser toutes les options de configuration aux valeurs par défaut",
Action: "Réinitialiser maintenant",
Confirm: "Confirmer la réinitialisation de tous les paramètres ?",
},
Clear: {
Title: "Supprimer toutes les données",
Title: "Effacer toutes les données",
SubTitle:
"Effacer toutes les données, y compris les conversations et les paramètres",
Action: "Supprimer",
Confirm: "Confirmer la suppression de toutes les données?",
"Effacer toutes les discussions et les données de configuration",
Action: "Effacer maintenant",
Confirm:
"Confirmer l'effacement de toutes les discussions et données de configuration ?",
},
},
Lang: {
Name: "Language", // ATTENTION : si vous souhaitez ajouter une nouvelle traduction, ne traduisez pas cette valeur, laissez-la sous forme de `Language`
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Toutes les langues",
},
Avatar: "Avatar",
FontSize: {
Title: "Taille des polices",
SubTitle: "Ajuste la taille de police du contenu de la conversation",
Title: "Taille de la police",
SubTitle: "Taille de la police pour le contenu des discussions",
},
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: {
Title: "Injecter des invites système",
SubTitle:
"Ajoute de force une invite système simulée de ChatGPT au début de la liste des messages pour chaque demande",
"Ajouter de manière forcée une invite système simulée de ChatGPT au début de chaque liste de messages",
},
InputTemplate: {
Title: "Template",
SubTitle: "Le message le plus récent sera ajouté à ce template.",
Title: "Prétraitement des entrées utilisateur",
SubTitle:
"Le dernier message de l'utilisateur sera intégré dans ce modèle",
},
Update: {
Version: (x: string) => `Version : ${x}`,
IsLatest: "Dernière version",
CheckUpdate: "Vérifier la mise à jour",
IsChecking: "Vérification de la mise à jour...",
FoundUpdate: (x: string) => `Nouvelle version disponible : ${x}`,
GoToUpdate: "Mise à jour",
Version: (x: string) => `Version actuelle : ${x}`,
IsLatest: "Vous avez la dernière version",
CheckUpdate: "Vérifier les mises à jour",
IsChecking: "Vérification des mises à jour en cours...",
FoundUpdate: (x: string) => `Nouvelle version trouvée : ${x}`,
GoToUpdate: "Aller à la mise à jour",
},
SendKey: "Clé d'envoi",
SendKey: "Touche d'envoi",
Theme: "Thème",
TightBorder: "Bordure serrée",
TightBorder: "Mode sans bordure",
SendPreviewBubble: {
Title: "Aperçu de l'envoi dans une bulle",
SubTitle: "Aperçu du Markdown dans une bulle",
Title: "Bulle d'aperçu",
SubTitle: "Aperçu du contenu Markdown dans la bulle d'aperçu",
},
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: {
Splash: {
Title: "Écran de masque",
Title: "Page de démarrage du masque",
SubTitle:
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
"Afficher la page de démarrage du masque lors de la création d'une nouvelle discussion",
},
Builtin: {
Title: "Masquer Les Assistants Intégrés",
SubTitle: "Masquer les assistants intégrés par défaut",
Title: "Masquer les masques intégrés",
SubTitle:
"Masquer les masques intégrés dans toutes les listes de masques",
},
},
Prompt: {
Disable: {
Title: "Désactiver la saisie semi-automatique",
SubTitle: "Appuyez sur / pour activer la saisie semi-automatique",
Title: "Désactiver la complétion automatique des invites",
SubTitle:
"Saisir / au début de la zone de texte pour déclencher la complétion automatique",
},
List: "Liste de prompts",
List: "Liste des invites personnalisées",
ListCount: (builtin: number, custom: number) =>
`${builtin} intégré, ${custom} personnalisé`,
`${builtin} intégrées, ${custom} définies par l'utilisateur`,
Edit: "Modifier",
Modal: {
Title: "Liste de prompts",
Add: "Ajouter un élément",
Search: "Rechercher des prompts",
Title: "Liste des invites",
Add: "Créer",
Search: "Rechercher des invites",
},
EditModal: {
Title: "Modifier le prompt",
Title: "Modifier les invites",
},
},
HistoryCount: {
Title: "Nombre de messages joints",
SubTitle: "Nombre de messages envoyés attachés par demande",
Title: "Nombre de messages historiques",
SubTitle: "Nombre de messages historiques envoyés avec chaque demande",
},
CompressThreshold: {
Title: "Seuil de compression de l'historique",
Title: "Seuil de compression des messages historiques",
SubTitle:
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
"Compresser les messages historiques lorsque leur longueur dépasse cette valeur",
},
Usage: {
Title: "Solde du compte",
Title: "Vérification du solde",
SubTitle(used: any, total: any) {
return `Épuisé ce mois-ci $${used}, abonnement $${total}`;
return `Utilisé ce mois-ci : $${used}, Total d'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",
Temperature: {
Title: "Température",
SubTitle: "Une valeur plus élevée rendra les réponses plus aléatoires",
Title: "Aléatoire (temperature)",
SubTitle: "Plus la valeur est élevée, plus les réponses sont aléatoires",
},
TopP: {
Title: "Top P",
Title: "Échantillonnage par noyau (top_p)",
SubTitle:
"Ne modifiez pas à moins que vous ne sachiez ce que vous faites",
"Semblable à l'aléatoire, mais ne pas modifier en même temps que l'aléatoire",
},
MaxTokens: {
Title: "Limite de Tokens",
SubTitle: "Longueur maximale des tokens d'entrée et des tokens générés",
Title: "Limite de réponse unique (max_tokens)",
SubTitle: "Nombre maximal de tokens utilisés pour une interaction unique",
},
PresencePenalty: {
Title: "Pénalité de présence",
Title: "Nouveauté du sujet (presence_penalty)",
SubTitle:
"Une valeur plus élevée augmentera la probabilité d'introduire de nouveaux sujets",
"Plus la valeur est élevée, plus il est probable d'élargir aux nouveaux sujets",
},
FrequencyPenalty: {
Title: "Pénalité de fréquence",
Title: "Pénalité de fréquence (frequency_penalty)",
SubTitle:
"Une valeur plus élevée diminuant la probabilité de répéter la même ligne",
"Plus la valeur est élevée, plus il est probable de réduire les répétitions",
},
},
Store: {
DefaultTopic: "Nouvelle conversation",
BotHello: "Bonjour ! Comment puis-je vous aider aujourd'hui ?",
Error: "Quelque chose s'est mal passé, veuillez réessayer plus tard.",
DefaultTopic: "Nouvelle discussion",
BotHello: "Comment puis-je vous aider ?",
Error: "Une erreur est survenue, veuillez réessayer plus tard",
Prompt: {
History: (content: string) =>
"Ceci est un résumé de l'historique des discussions entre l'IA et l'utilisateur : " +
content,
"Voici le résumé de la discussion précédente : " + content,
Topic:
"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.",
"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 »",
Summarize:
"Résumez brièvement nos discussions en 200 mots ou moins pour les utiliser comme prompt de contexte futur.",
"Faites un résumé succinct de la discussion, à utiliser comme prompt de contexte ultérieur, en moins de 200 mots",
},
},
Copy: {
Success: "Copié dans le presse-papiers",
Failed:
"La copie a échoué, veuillez accorder l'autorisation d'accès au presse-papiers",
Failed: "Échec de la copie, veuillez autoriser l'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: {
Toast: (x: any) => `Avec ${x} contextes de prompts`,
Edit: "Contextes et mémoires de prompts",
Add: "Ajouter un prompt",
Toast: (x: any) => `Contient ${x} invites prédéfinies`,
Edit: "Paramètres de la discussion actuelle",
Add: "Ajouter une discussion",
Clear: "Contexte effacé",
Revert: "Restaurer le contexte",
},
Plugin: {
Name: "Extension",
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Eres un asistente que",
Sysmessage: "Vous êtes un assistant",
},
SearchChat: {
Name: "Recherche",
Page: {
Title: "Rechercher dans l'historique des discussions",
Search: "Entrez le mot-clé de recherche",
NoResult: "Aucun résultat trouvé",
NoData: "Aucune donnée",
Loading: "Chargement",
SubTitle: (count: number) => `${count} résultats trouvés`,
},
Item: {
View: "Voir",
},
},
Mask: {
Name: "Masque",
Page: {
Title: "Modèle de prompt",
SubTitle: (count: number) => `${count} modèles de prompts`,
Search: "Rechercher des modèles",
Title: "Masques de rôle prédéfinis",
SubTitle: (count: number) => `${count} définitions de rôle prédéfinies`,
Search: "Rechercher des masques de rôle",
Create: "Créer",
},
Item: {
Info: (count: number) => `${count} prompts`,
Info: (count: number) => `Contient ${count} discussions prédéfinies`,
Chat: "Discussion",
View: "Vue",
View: "Voir",
Edit: "Modifier",
Delete: "Supprimer",
DeleteConfirm: "Confirmer la suppression?",
DeleteConfirm: "Confirmer la suppression ?",
},
EditModal: {
Title: (readonly: boolean) =>
`Modifier le modèle de prompt ${readonly ? "(en lecture seule)" : ""}`,
Download: "Télécharger",
Clone: "Dupliquer",
`Modifier le masque prédéfini ${readonly ? " (lecture seule)" : ""}`,
Download: "Télécharger le masque",
Clone: "Cloner le masque",
},
Config: {
Avatar: "Avatar de lassistant",
Name: "Nom de lassistant",
Avatar: "Avatar du rôle",
Name: "Nom du rôle",
Sync: {
Title: "Utiliser la configuration globale",
SubTitle: "Utiliser la configuration globale dans cette conversation",
Confirm: "Voulez-vous definir votre configuration personnalisée ?",
Title: "Utiliser les paramètres globaux",
SubTitle:
"Cette discussion utilise-t-elle les paramètres du modèle globaux ?",
Confirm:
"Les paramètres personnalisés de cette discussion seront automatiquement remplacés. Confirmer l'activation des paramètres globaux ?",
},
HideContext: {
Title: "Masquer les invites contextuelles",
SubTitle: "Ne pas afficher les instructions contextuelles dans le chat",
Title: "Masquer les discussions prédéfinies",
SubTitle:
"Les discussions prédéfinies ne seront pas affichées dans l'interface de discussion après masquage",
},
Share: {
Title: "Partager ce masque",
SubTitle: "Générer un lien vers ce masque",
SubTitle: "Générer un lien direct pour ce masque",
Action: "Copier le lien",
},
},
},
NewChat: {
Return: "Retour",
Skip: "Passer",
Title: "Choisir un assitant",
SubTitle: "Discutez avec l'âme derrière le masque",
More: "En savoir plus",
NotShow: "Ne pas afficher à nouveau",
Skip: "Commencer directement",
NotShow: "Ne plus afficher",
ConfirmNoShow:
"Confirmez-vous vouloir désactiver cela? Vous pouvez le réactiver plus tard dans les paramètres.",
"Confirmer la désactivation ? Vous pourrez réactiver cette option à tout moment 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: {
@@ -296,9 +561,17 @@ const fr: PartialLocaleType = {
Cancel: "Annuler",
Close: "Fermer",
Create: "Créer",
Edit: "Éditer",
Edit: "Modifier",
Export: "Exporter",
Import: "Importer",
Sync: "Synchroniser",
Config: "Configurer",
},
Exporter: {
Description: {
Title:
"Seuls les messages après avoir effacé le contexte seront affichés",
},
Model: "Modèle",
Messages: "Messages",
Topic: "Sujet",

View File

@@ -1,228 +1,261 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "./index";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const id: PartialLocaleType = {
WIP: "Coming Soon...",
Error: {
Unauthorized:
"Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
Unauthorized: isApp
? "API Key tidak valid terdeteksi, silakan periksa apakah API Key telah dikonfigurasi dengan benar 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: {
Title: "Diperlukan Kode Akses",
Tips: "Masukkan kode akses di bawah",
SubTips: "Atau masukkan kunci API OpenAI Anda",
Input: "Kode Akses",
Title: "Kebutuhan Kata Sandi",
Tips: "Administrator telah mengaktifkan verifikasi kata sandi, silakan masukkan kode akses di bawah ini",
SubTips: "Atau masukkan kunci API OpenAI atau Google Anda",
Input: "Masukkan kode akses di sini",
Confirm: "Konfirmasi",
Later: "Nanti",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} pesan`,
ChatItemCount: (count: number) => `${count} percakapan`,
},
Chat: {
SubTitle: (count: number) => `${count} pesan`,
SubTitle: (count: number) => `Total ${count} percakapan`,
EditMessage: {
Title: "Edit Riwayat Pesan",
Topic: {
Title: "Topik Obrolan",
SubTitle: "Ubah topik obrolan saat ini",
},
},
Actions: {
ChatList: "Buka Daftar Chat",
CompressedHistory: "Ekspor Riwayat Terkompresi",
Export: "Ekspor Semua Pesan sebagai Markdown",
ChatList: "Lihat daftar pesan",
CompressedHistory: "Lihat riwayat Prompt yang dikompresi",
Export: "Ekspor riwayat obrolan",
Copy: "Salin",
Stop: "Berhenti",
Retry: "Coba Lagi",
Pin: "Pin",
PinToastContent: "2 pesan telah ditandai",
Retry: "Coba lagi",
Pin: "Sematkan",
PinToastContent: "1 percakapan telah disematkan ke prompt default",
PinToastAction: "Lihat",
Delete: "Hapus",
Edit: "Edit",
},
Commands: {
new: "Mulai Chat Baru",
newm: "Mulai Chat Baru dengan Masks",
next: "Chat Selanjutnya",
prev: "Chat Sebelumnya",
clear: "Bersihkan Percakapan",
del: "Hapus Chat",
new: "Obrolan Baru",
newm: "Buat Obrolan Baru dari Masker",
next: "Obrolan Berikutnya",
prev: "Obrolan Sebelumnya",
clear: "Hapus Konteks",
del: "Hapus Obrolan",
},
InputActions: {
Stop: "Berhenti",
ToBottom: "Ke Bagian Bawah",
Stop: "Hentikan Respons",
ToBottom: "Gulir ke bawah",
Theme: {
auto: "Otomatis",
light: "Tema Terang",
dark: "Tema Gelap",
auto: "Tema Otomatis",
light: "Mode Terang",
dark: "Mode Gelap",
},
Prompt: "Prompts",
Masks: "Masks",
Clear: "Bersihkan Percakapan",
Settings: "Pengaturan",
Prompt: "Perintah Cepat",
Masks: "Semua Masker",
Clear: "Hapus Obrolan",
Settings: "Pengaturan Obrolan",
UploadImage: "Unggah Gambar",
},
Rename: "Ubah Nama Chat",
Typing: "Mengetik...",
Rename: "Ganti Nama Obrolan",
Typing: "Sedang Mengetik",
Input: (submitKey: string) => {
var inputHints = `${submitKey} untuk mengirim`;
var inputHints = `${submitKey} kirim`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter untuk membalut";
inputHints += "Shift + Enter untuk baris baru";
}
return (
inputHints + ", / untuk mencari prompt, : untuk menggunakan perintah"
);
return inputHints + "/ untuk melengkapi, : untuk memicu perintah";
},
Send: "Kirim",
Config: {
Reset: "Reset ke Default",
SaveAs: "Simpan sebagai Masks",
Reset: "Hapus Memori",
SaveAs: "Simpan sebagai Masker",
},
IsContext: "Prompt Default",
},
Export: {
Title: "Ekspor Pesan",
Title: "Bagikan Riwayat Obrolan",
Copy: "Salin Semua",
Download: "Unduh",
MessageFromYou: "Pesan dari Anda",
MessageFromChatGPT: "Pesan dari ChatGPT",
Download: "Unduh File",
Share: "Bagikan ke ShareGPT",
MessageFromYou: "Pengguna",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "Format Ekspor",
SubTitle: "Markdown atau Gambar PNG",
SubTitle: "Dapat mengekspor teks Markdown atau gambar PNG",
},
IncludeContext: {
Title: "Sertakan Konteks",
SubTitle: "Apakah akan menyertakan masks",
Title: "Sertakan Konteks Masker",
SubTitle: "Apakah akan menampilkan konteks masker dalam pesan",
},
Steps: {
Select: "Pilih",
Preview: "Pratinjau",
Preview: "Prabaca",
},
Image: {
Toast: "Sedang Membuat Screenshot",
Modal: "Tekan lama atau klik kanan untuk menyimpan gambar",
},
},
Select: {
Search: "Cari",
Search: "Cari Pesan",
All: "Pilih Semua",
Latest: "Pilih Terbaru",
Clear: "Bersihkan",
Latest: "Beberapa Terbaru",
Clear: "Hapus Pilihan",
},
Memory: {
Title: "Prompt Memori",
EmptyContent: "Belum ada yang tersedia.",
Send: "Kirim Memori",
Copy: "Salin Memori",
Reset: "Reset",
ResetConfirm:
"Jika Anda mereset, riwayat obrolan saat ini dan memori historis akan dihapus. Apakah Anda yakin ingin melakukan reset?",
Title: "Ringkasan Sejarah",
EmptyContent: "Isi percakapan terlalu pendek, tidak perlu dirangkum",
Send: "Otomatis kompres riwayat obrolan dan kirim sebagai konteks",
Copy: "Salin Ringkasan",
Reset: "[unused]",
ResetConfirm: "Konfirmasi untuk menghapus ringkasan sejarah?",
},
Home: {
NewChat: "Obrolan Baru",
DeleteChat: "Anda yakin ingin menghapus percakapan yang dipilih?",
DeleteChat: "Konfirmasi untuk menghapus percakapan yang dipilih?",
DeleteToast: "Percakapan telah dihapus",
Revert: "Kembali",
Revert: "Batalkan",
},
Settings: {
Title: "Pengaturan",
SubTitle: "Semua Pengaturan",
SubTitle: "Semua opsi pengaturan",
Danger: {
Reset: {
Title: "Setel Ulang Semua Pengaturan",
SubTitle: "Mengembalikan semua pengaturan ke nilai default",
Action: "Setel Ulang",
Confirm:
"Anda yakin ingin mengembalikan semua pengaturan ke nilai default?",
Title: "Atur Ulang Semua Pengaturan",
SubTitle: "Atur ulang semua opsi pengaturan ke nilai default",
Action: "Atur Ulang Sekarang",
Confirm: "Konfirmasi untuk mengatur ulang semua pengaturan?",
},
Clear: {
Title: "Hapus Semua Data",
SubTitle: "Semua data yang tersimpan secara lokal akan dihapus",
Action: "Hapus",
SubTitle: "Hapus semua data obrolan dan pengaturan",
Action: "Hapus Sekarang",
Confirm:
"Apakah Anda yakin ingin menghapus semua data yang tersimpan secara lokal?",
"Konfirmasi untuk menghapus semua data obrolan dan pengaturan?",
},
},
Lang: {
Name: "Bahasa", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Language", // PERHATIAN: jika Anda ingin menambahkan terjemahan baru, harap jangan terjemahkan nilai ini, biarkan sebagai `Language`
All: "Semua Bahasa",
},
Avatar: "Avatar",
FontSize: {
Title: "Ukuran Font",
SubTitle: "Ubah ukuran font konten chat",
SubTitle: "Ukuran font untuk konten obrolan",
},
FontFamily: {
Title: "Font Obrolan",
SubTitle:
"Font dari konten obrolan, biarkan kosong untuk menerapkan font default global",
Placeholder: "Nama Font",
},
InjectSystemPrompts: {
Title: "Suntikkan Petunjuk Sistem",
Title: "Suntikkan Pesan Sistem",
SubTitle:
"Tambahkan petunjuk simulasi sistem ChatGPT di awal daftar pesan yang diminta dalam setiap permintaan",
"Memaksa menambahkan pesan sistem simulasi ChatGPT di awal daftar pesan setiap permintaan",
},
InputTemplate: {
Title: "Template Input",
SubTitle: "Pesan baru akan diisi menggunakan template ini",
Title: "Pra-pemrosesan Input Pengguna",
SubTitle: "Pesan terbaru pengguna akan diisi ke template ini",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Versi terbaru",
Version: (x: string) => `Versi Saat Ini: ${x}`,
IsLatest: "Sudah versi terbaru",
CheckUpdate: "Periksa Pembaruan",
IsChecking: "Memeriksa pembaruan...",
FoundUpdate: (x: string) => `Versi terbaru ditemukan: ${x}`,
GoToUpdate: "Perbarui Sekarang",
IsChecking: "Sedang memeriksa pembaruan...",
FoundUpdate: (x: string) => `Versi Baru Ditemukan: ${x}`,
GoToUpdate: "Pergi ke Pembaruan",
},
SendKey: "Kunci Kirim",
Theme: "Tema",
TightBorder: "Mode Tanpa Border",
SendPreviewBubble: {
Title: "Preview Bubble",
SubTitle: "Pratinjau konten Markdown di bubble pratinjau",
},
AutoGenerateTitle: {
Title: "Hasilkan Judul Otomatis",
SubTitle: "Hasilkan judul yang sesuai berdasarkan konten percakapan",
Title: "Otomatis Membuat Judul",
SubTitle: "Membuat judul yang sesuai berdasarkan konten obrolan",
},
Sync: {
CloudState: "Pembaruan Terakhir",
CloudState: "Data Cloud",
NotSyncYet: "Belum disinkronkan",
Success: "Sinkronisasi Berhasil",
Fail: "Sinkronisasi Gagal",
Config: {
Modal: {
Title: "Konfigurasi Sinkronisasi",
Title: "Konfigurasi Sinkronisasi Cloud",
Check: "Periksa Ketersediaan",
},
SyncType: {
Title: "Tipe Sinkronisasi",
SubTitle: "Pilih layanan sinkronisasi favorit Anda",
Title: "Jenis Sinkronisasi",
SubTitle: "Pilih server sinkronisasi favorit",
},
Proxy: {
Title: "Aktifkan Proxy CORS",
Title: "Aktifkan Proxy",
SubTitle:
"Aktifkan Proxy untuk menghindari pembatasan atau pemblokiran lintas sumber",
"Saat menyinkronkan di browser, proxy harus diaktifkan untuk menghindari pembatasan lintas domain",
},
ProxyUrl: {
Title: "Lokasi Titik Akhir Proxy CORS",
SubTitle: "Hanya berlaku untuk Proxy CORS bawaan untuk proyek ini",
Title: "Alamat Proxy",
SubTitle: "Hanya berlaku untuk proxy lintas domain bawaan proyek ini",
},
WebDav: {
Endpoint: "Lokasi Titik Akhir WebDAV",
UserName: "User Pengguna",
Endpoint: "Alamat WebDAV",
UserName: "Nama Pengguna",
Password: "Kata Sandi",
},
UpStash: {
Endpoint: "Url REST Redis UpStash",
UserName: "Nama Cadangan",
Password: "Token REST Redis UpStash",
},
},
},
SendKey: "Kirim",
Theme: "Tema",
TightBorder: "Batas Ketat",
SendPreviewBubble: {
Title: "Pratinjau Obrolan",
SubTitle: "Pratinjau Obrolan dengan markdown",
LocalState: "Data Lokal",
Overview: (overview: any) => {
return `${overview.chat} percakapan, ${overview.message} pesan, ${overview.prompt} prompt, ${overview.mask} masker`;
},
ImportFailed: "Impor Gagal",
},
Mask: {
Splash: {
Title: "Layar Pembuka Masks",
SubTitle:
"Tampilkan layar pembuka masks sebelum memulai percakapan baru",
Title: "Halaman Awal Masker",
SubTitle: "Tampilkan halaman awal masker saat memulai obrolan baru",
},
Builtin: {
Title: "Sembunyikan Masks Bawaan",
SubTitle: "Sembunyikan Masks bawaan dari daftar masks",
Title: "Sembunyikan Masker Bawaan",
SubTitle: "Sembunyikan masker bawaan dari semua daftar masker",
},
},
Prompt: {
Disable: {
Title: "Nonaktifkan Otomatisasi",
SubTitle: "Aktifkan/Matikan otomatisasi",
Title: "Nonaktifkan Pelengkapan Prompt Otomatis",
SubTitle:
"Ketik / di awal kotak input untuk memicu pelengkapan otomatis",
},
List: "Daftar Prompt",
List: "Daftar Prompt Kustom",
ListCount: (builtin: number, custom: number) =>
`${builtin} bawaan, ${custom} penggunaan khusus`,
`Bawaan ${builtin} item, pengguna ${custom} item`,
Edit: "Edit",
Modal: {
Title: "Daftar Prompt",
Add: "Tambahkan",
Add: "Baru",
Search: "Cari Prompt",
},
EditModal: {
@@ -230,155 +263,308 @@ const id: PartialLocaleType = {
},
},
HistoryCount: {
Title: "Jumlah Pesan Riwayat",
SubTitle: "Jumlah pesan yang akan dikirim setiap permintaan",
Title: "Jumlah Pesan Sejarah",
SubTitle: "Jumlah pesan sejarah yang dibawa setiap permintaan",
},
CompressThreshold: {
Title: "Batas Kompresi Riwayat",
Title: "Ambang Batas Kompresi Pesan Sejarah",
SubTitle:
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
"Ketika pesan sejarah yang tidak terkompresi melebihi nilai ini, akan dikompresi",
},
Usage: {
Title: "Saldo Akun",
Title: "Cek Saldo",
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",
Temperature: {
Title: "Suhu",
SubTitle: "Semakin tinggi nilainya, semakin acak keluarannya",
Title: "Randomness (temperature)",
SubTitle: "Semakin tinggi nilainya, semakin acak responsnya",
},
TopP: {
Title: "Top P",
SubTitle: "Tidak mengubah nilai dengan suhu",
Title: "Sampling Inti (top_p)",
SubTitle:
"Mirip dengan randomness, tetapi jangan ubah bersama randomness",
},
MaxTokens: {
Title: "Token Maksimum",
SubTitle: "Panjang maksimum token input dan output",
Title: "Batas Token Per Respons",
SubTitle: "Jumlah token maksimum yang digunakan per interaksi",
},
PresencePenalty: {
Title: "Penalti Kehadiran",
SubTitle: "Semakin tinggi nilai, semakin mungkin topik baru muncul",
Title: "Kedekatan Topik (presence_penalty)",
SubTitle:
"Semakin tinggi nilainya, semakin besar kemungkinan memperluas ke topik baru",
},
FrequencyPenalty: {
Title: "Penalti Frekuensi",
Title: "Hukuman Frekuensi (frequency_penalty)",
SubTitle:
"Semakin tinggi nilai, semakin rendah kemungkinan penggunaan ulang baris yang sama",
"Semakin tinggi nilainya, semakin besar kemungkinan mengurangi kata-kata yang berulang",
},
},
Store: {
DefaultTopic: "Percakapan Baru",
BotHello: "Halo! Bagaimana saya bisa membantu Anda hari ini?",
Error: "Terjadi kesalahan, silakan coba lagi nanti.",
DefaultTopic: "Obrolan Baru",
BotHello: "Ada yang bisa saya bantu?",
Error: "Terjadi kesalahan, coba lagi nanti",
Prompt: {
History: (content: string) =>
"Ini adalah ringkasan singkat dari riwayat percakapan: " + content,
"Ini adalah ringkasan obrolan sebelumnya sebagai latar belakang: " +
content,
Topic:
"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.",
"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'",
Summarize:
"Buat ringkasan percakapan dalam 200 kata yang akan digunakan sebagai promp di masa depan.",
"Berikan ringkasan singkat tentang konten obrolan, untuk digunakan sebagai prompt konteks selanjutnya, dalam 200 kata atau kurang",
},
},
Copy: {
Success: "Tersalin ke clipboard",
Failed:
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
Success: "Telah disalin ke clipboard",
Failed: "Gagal menyalin, mohon berikan izin clipboard",
},
Download: {
Success: "Konten berhasil diunduh ke direktori Anda.",
Success: "Konten telah diunduh ke direktori Anda.",
Failed: "Unduhan gagal.",
},
Context: {
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
Toast: (x: any) => `Berisi ${x} prompt preset`,
Edit: "Pengaturan Obrolan Saat Ini",
Add: "Tambahkan Promp",
Clear: "Bersihkan Konteks",
Revert: "Kembali ke Posisi Sebelumnya",
Add: "Tambah Obrolan",
Clear: "Konteks telah dihapus",
Revert: "Kembalikan Konteks",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Anda adalah asisten yang",
Sysmessage: "Anda adalah seorang asisten",
},
Mask: {
Name: "Masks",
SearchChat: {
Name: "Cari",
Page: {
Title: "Template Promp",
SubTitle: (count: number) => `${count} template prompt`,
Search: "Cari template",
Create: "Buat",
Title: "Cari riwayat obrolan",
Search: "Masukkan kata kunci pencarian",
NoResult: "Tidak ada hasil ditemukan",
NoData: "Tidak ada data",
Loading: "Memuat",
SubTitle: (count: number) => `Ditemukan ${count} hasil`,
},
Item: {
Info: (count: number) => `${count} prompt`,
View: "Lihat",
},
},
Mask: {
Name: "Masker",
Page: {
Title: "Preset Karakter Masker",
SubTitle: (count: number) => `${count} definisi karakter preset`,
Search: "Cari Masker Karakter",
Create: "Buat Baru",
},
Item: {
Info: (count: number) => `Berisi ${count} obrolan preset`,
Chat: "Obrolan",
View: "Lihat",
Edit: "Edit",
Delete: "Hapus",
DeleteConfirm: "Anda yakin ingin menghapus?",
DeleteConfirm: "Konfirmasi penghapusan?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Template Prompt ${readonly ? "(hanya baca)" : ""}`,
Download: "Unduh",
Clone: "Duplikat",
`Edit Masker Preset ${readonly ? "(Hanya Baca)" : ""}`,
Download: "Unduh Preset",
Clone: "Klon Preset",
},
Config: {
Avatar: "Avatar Bot",
Name: "Nama Bot",
Avatar: "Avatar Karakter",
Name: "Nama Karakter",
Sync: {
Title: "Gunakan Konfigurasi Global",
SubTitle: "Gunakan konfigurasi global dalam percakapan ini",
Title: "Gunakan Pengaturan Global",
SubTitle:
"Apakah obrolan saat ini akan menggunakan pengaturan model global?",
Confirm:
"Pastikan untuk mengganti konfigurasi kustom dengan konfigurasi global?",
"Pengaturan kustom obrolan saat ini akan ditimpa secara otomatis, konfirmasi untuk mengaktifkan pengaturan global?",
},
HideContext: {
Title: "Sembunyikan Prompt Konteks",
SubTitle: "Tidak menampilkan prompt konteks dalam obrolan",
Title: "Sembunyikan Obrolan Preset",
SubTitle:
"Setelah disembunyikan, obrolan preset tidak akan muncul di antarmuka obrolan",
},
Share: {
Title: "Bagikan Masks Ini",
SubTitle: "Buat tautan untuk masks ini",
Title: "Bagikan Masker Ini",
SubTitle: "Hasilkan tautan langsung ke masker ini",
Action: "Salin Tautan",
},
},
},
NewChat: {
Return: "Kembali",
Skip: "Lewati",
Title: "Pilih Masks",
SubTitle: "Berkonversasilah dengan diri Anda di balik masks",
More: "Lebih Lanjut",
NotShow: "Jangan Tampilkan Sekarang",
Skip: "Mulai Sekarang",
NotShow: "Jangan Tampilkan Lagi",
ConfirmNoShow:
"Pastikan untuk menonaktifkannya? Anda dapat mengaktifkannya nanti melalui pengaturan.",
"Konfirmasi untuk menonaktifkan? Setelah dinonaktifkan, Anda dapat mengaktifkannya kembali kapan saja di 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: {
Confirm: "Konfirmasi",
Cancel: "Batal",
Close: "Tutup",
Create: "Buat",
Create: "Buat Baru",
Edit: "Edit",
Export: "Ekspor",
Import: "Impor",
Sync: "Sinkronkan",
Config: "Konfigurasi",
},
Exporter: {
Description: {
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan"
},
Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan",
},
Model: "Model",
Messages: "Pesan",
Topic: "Topik",
Time: "Tanggal & Waktu",
},
URLCommand: {
Code: "Kode akses terdeteksi dari url, konfirmasi untuk mendaftar ? ",
Settings: "Pengaturan terdeteksi dari url, konfirmasi untuk diterapkan ?",
Time: "Waktu",
},
};

View File

@@ -1,238 +1,581 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const it: PartialLocaleType = {
WIP: "Work in progress...",
Error: {
Unauthorized:
"Accesso non autorizzato, inserire il codice di accesso nella [pagina](/#/auth) delle impostazioni.",
Unauthorized: isApp
? "API Key non valido rilevato. Vai alla pagina [Impostazioni](/#/settings) per controllare se l'API Key è configurata correttamente."
: "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: {
ChatItemCount: (count: number) => `${count} messaggi`,
ChatItemCount: (count: number) => `${count} conversazioni`,
},
Chat: {
SubTitle: (count: number) => `${count} messaggi con ChatGPT`,
Actions: {
ChatList: "Vai alla Chat List",
CompressedHistory: "Prompt di memoria della cronologia compressa",
Export: "Esportazione di tutti i messaggi come Markdown",
Copy: "Copia",
Stop: "Stop",
Retry: "Riprova",
Delete: "Delete",
SubTitle: (count: number) => `Totale ${count} conversazioni`,
EditMessage: {
Title: "Modifica cronologia messaggi",
Topic: {
Title: "Argomento della chat",
SubTitle: "Modifica l'argomento della chat corrente",
},
},
Rename: "Rinomina Chat",
Typing: "Typing…",
Actions: {
ChatList: "Visualizza l'elenco dei messaggi",
CompressedHistory: "Visualizza la cronologia Prompt compressa",
Export: "Esporta la cronologia chat",
Copy: "Copia",
Stop: "Interrompi",
Retry: "Riprova",
Pin: "Fissa",
PinToastContent: "1 conversazione fissata ai suggerimenti predefiniti",
PinToastAction: "Visualizza",
Delete: "Elimina",
Edit: "Modifica",
},
Commands: {
new: "Nuova chat",
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) => {
var inputHints = `Scrivi qualcosa e premi ${submitKey} per inviare`;
var inputHints = `${submitKey} per inviare`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", premi Shift + Enter per andare a capo";
inputHints += "Shift + Enter per andare a capo";
}
return inputHints;
return (
inputHints +
"/ per attivare il completamento automatico, : per attivare il comando"
);
},
Send: "Invia",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
Reset: "Pulisci memoria",
SaveAs: "Salva come maschera",
},
IsContext: "Suggerimenti predefiniti",
},
Export: {
Title: "Tutti i messaggi",
Title: "Condividi cronologia chat",
Copy: "Copia tutto",
Download: "Scarica",
MessageFromYou: "Messaggio da te",
MessageFromChatGPT: "Messaggio da ChatGPT",
Download: "Scarica file",
Share: "Condividi su ShareGPT",
MessageFromYou: "Utente",
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: {
Title: "Prompt di memoria",
EmptyContent: "Vuoto.",
Copy: "Copia tutto",
Send: "Send Memory",
Reset: "Reset Session",
ResetConfirm:
"Ripristinare cancellerà la conversazione corrente e la cronologia di memoria. Sei sicuro che vuoi riavviare?",
Title: "Riassunto storico",
EmptyContent:
"Il contenuto della conversazione è troppo breve, nessun riassunto necessario",
Send: "Comprimi automaticamente la cronologia chat e inviala come contesto",
Copy: "Copia riassunto",
Reset: "[unused]",
ResetConfirm: "Confermi la cancellazione del riassunto storico?",
},
Home: {
NewChat: "Nuova Chat",
DeleteChat: "Confermare la cancellazione della conversazione selezionata?",
DeleteToast: "Chat Cancellata",
Revert: "Revert",
NewChat: "Nuova chat",
DeleteChat: "Confermi l'eliminazione della conversazione selezionata?",
DeleteToast: "Conversazione eliminata",
Revert: "Annulla",
},
Settings: {
Title: "Impostazioni",
SubTitle: "Tutte le impostazioni",
SubTitle: "Tutte le opzioni di impostazione",
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: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Language", // ATTENZIONE: se vuoi aggiungere una nuova traduzione, non tradurre questo valore, lascialo come `Language`
All: "Tutte le lingue",
},
Avatar: "Avatar",
FontSize: {
Title: "Dimensione carattere",
SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat",
Title: "Dimensione del carattere",
SubTitle: "Dimensione del carattere per il 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: {
Title: "Inserisci Prompts di Sistema",
Title: "Inserisci suggerimenti di sistema",
SubTitle:
"Aggiungi forzatamente un prompt di sistema simulato di ChatGPT all'inizio della lista dei messaggi per ogni richiesta",
"Aggiungi forzatamente un suggerimento 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: {
Version: (x: string) => `Versione: ${x}`,
IsLatest: "Ultima versione",
Version: (x: string) => `Versione attuale: ${x}`,
IsLatest: "È l'ultima versione",
CheckUpdate: "Controlla aggiornamenti",
IsChecking: "Sto controllando gli aggiornamenti...",
FoundUpdate: (x: string) => `Trovata nuova versione: ${x}`,
GoToUpdate: "Aggiorna",
IsChecking: "Verifica aggiornamenti in corso...",
FoundUpdate: (x: string) => `Nuova versione trovata: ${x}`,
GoToUpdate: "Vai all'aggiornamento",
},
SendKey: "Tasto invia",
SendKey: "Tasto di invio",
Theme: "Tema",
TightBorder: "Schermo intero",
TightBorder: "Modalità senza bordi",
SendPreviewBubble: {
Title: "Anteprima di digitazione",
SubTitle: "Preview markdown in bubble",
Title: "Bolla di anteprima",
SubTitle: "Anteprima del contenuto Markdown nella bolla di anteprima",
},
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: {
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Title: "Pagina di avvio delle maschere",
SubTitle:
"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: {
Disable: {
Title: "Disabilita l'auto completamento",
SubTitle: "Input / per attivare il completamento automatico",
Title: "Disabilita completamento automatico dei suggerimenti",
SubTitle:
"Inserisci / all'inizio della casella di input per attivare il completamento automatico",
},
List: "Elenco dei suggerimenti",
List: "Elenco dei suggerimenti personalizzati",
ListCount: (builtin: number, custom: number) =>
`${builtin} built-in, ${custom} user-defined`,
`${builtin} predefiniti, ${custom} definiti dall'utente`,
Edit: "Modifica",
Modal: {
Title: "Prompt List",
Add: "Add One",
Search: "Search Prompts",
Title: "Elenco dei suggerimenti",
Add: "Nuovo",
Search: "Cerca suggerimenti",
},
EditModal: {
Title: "Edit Prompt",
Title: "Modifica suggerimenti",
},
},
HistoryCount: {
Title: "Conteggio dei messaggi allegati",
SubTitle: "Numero di messaggi inviati allegati per richiesta",
Title: "Numero di messaggi storici inclusi",
SubTitle: "Numero di messaggi storici inclusi in ogni richiesta",
},
CompressThreshold: {
Title: "Soglia di compressione della cronologia",
Title: "Soglia di compressione dei messaggi storici",
SubTitle:
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
"Quando i messaggi storici non compressi superano questo valore, verranno compressi",
},
Usage: {
Title: "Bilancio Account",
Title: "Verifica saldo",
SubTitle(used: any, total: any) {
return `Attualmente usato in questo mese $${used}, soglia massima $${total}`;
return `Utilizzato questo mese $${used}, totale abbonamento $${total}`;
},
IsChecking: "Controllando...",
Check: "Controlla ancora",
NoAccess: "Inserire la chiave API per controllare il saldo",
IsChecking: "Verifica in corso…",
Check: "Verifica di nuovo",
NoAccess:
"Inserisci API Key o password di accesso per visualizzare il saldo",
},
Model: "Modello GPT",
Access: {
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: {
Title: "Temperature",
SubTitle: "Un valore maggiore rende l'output più casuale",
Title: "Casualità (temperature)",
SubTitle: "Valore più alto, risposte più casuali",
},
TopP: {
Title: "Campionamento nucleare (top_p)",
SubTitle:
"Simile alla casualità, ma non cambiarlo insieme alla casualità",
},
MaxTokens: {
Title: "Token massimi",
SubTitle: "Lunghezza massima dei token in ingresso e dei token generati",
Title: "Limite di token per risposta (max_tokens)",
SubTitle: "Numero massimo di token per ogni interazione",
},
PresencePenalty: {
Title: "Penalità di presenza",
Title: "Novità del tema (presence_penalty)",
SubTitle:
"Un valore maggiore aumenta la probabilità di parlare di nuovi argomenti",
"Valore più alto, maggiore possibilità di espandere a nuovi argomenti",
},
FrequencyPenalty: {
Title: "Penalità di frequenza",
Title: "Penalità di frequenza (frequency_penalty)",
SubTitle:
"Un valore maggiore che diminuisce la probabilità di ripetere la stessa riga",
"Valore più alto, maggiore possibilità di ridurre le ripetizioni",
},
},
Store: {
DefaultTopic: "Nuova conversazione",
BotHello: "Ciao, come posso aiutarti oggi?",
Error: "Qualcosa è andato storto, riprova più tardi.",
DefaultTopic: "Nuova chat",
BotHello: "Come posso aiutarti?",
Error: "Si è verificato un errore, riprova più tardi",
Prompt: {
History: (content: string) =>
"Questo è un riassunto della cronologia delle chat tra l'IA e l'utente:" +
content,
"Questo è un riassunto della chat storica come contesto: " + content,
Topic:
"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",
"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'",
Summarize:
"Riassumi brevemente la nostra discussione in 200 caratteri o meno per usarla come spunto per una futura conversazione.",
"Riassumi brevemente il contenuto della conversazione come prompt di contesto per il seguito, mantenendolo entro 200 parole",
},
},
Copy: {
Success: "Copiato sugli appunti",
Failed:
"Copia fallita, concedere l'autorizzazione all'accesso agli appunti",
Success: "Copiato negli appunti",
Failed: "Copia fallita, concedi i permessi per gli appunti",
},
Download: {
Success: "Contenuto scaricato nella tua directory.",
Failed: "Download fallito.",
},
Context: {
Toast: (x: any) => `Con ${x} prompts contestuali`,
Edit: "Prompt contestuali e di memoria",
Add: "Aggiungi altro",
Toast: (x: any) => `Include ${x} suggerimenti predefiniti`,
Edit: "Impostazioni della conversazione attuale",
Add: "Aggiungi una conversazione",
Clear: "Contesto cancellato",
Revert: "Ripristina contesto",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Sei un assistente che",
Sysmessage: "Sei un assistente",
},
Mask: {
Name: "Mask",
SearchChat: {
Name: "Cerca",
Page: {
Title: "Prompt Template",
SubTitle: (count: number) => `${count} prompt templates`,
Search: "Search Templates",
Create: "Create",
Title: "Cerca nei messaggi",
Search: "Inserisci parole chiave per la ricerca",
NoResult: "Nessun risultato trovato",
NoData: "Nessun dato",
Loading: "Caricamento in corso",
SubTitle: (count: number) => `Trovati ${count} risultati`,
},
Item: {
Info: (count: number) => `${count} prompts`,
Chat: "Chat",
View: "View",
Edit: "Edit",
Delete: "Delete",
DeleteConfirm: "Confirm to delete?",
View: "Visualizza",
},
},
Mask: {
Name: "Maschera",
Page: {
Title: "Maschere dei ruoli predefiniti",
SubTitle: (count: number) => `${count} definizioni di ruoli predefiniti`,
Search: "Cerca maschere di ruolo",
Create: "Crea nuovo",
},
Item: {
Info: (count: number) => `Include ${count} conversazioni predefinite`,
Chat: "Conversazione",
View: "Visualizza",
Edit: "Modifica",
Delete: "Elimina",
DeleteConfirm: "Confermi eliminazione?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Clone: "Clone",
`Modifica maschera predefinita ${readonly ? "(sola lettura)" : ""}`,
Download: "Scarica predefinito",
Clone: "Clona predefinito",
},
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Avatar: "Avatar del ruolo",
Name: "Nome del ruolo",
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: {
Return: "Return",
Skip: "Skip",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Not Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
Return: "Torna",
Skip: "Inizia subito",
NotShow: "Non mostrare più",
ConfirmNoShow:
"Confermi di disabilitare? Dopo la disabilitazione, puoi riattivare in qualsiasi momento dalle impostazioni.",
Title: "Scegli una maschera",
SubTitle: "Inizia ora e interagisci con il pensiero dietro la maschera",
More: "Vedi tutto",
},
URLCommand: {
Code: "Codice di accesso rilevato nel link, riempirlo automaticamente?",
Settings:
"Impostazioni predefinite rilevate nel link, riempirle automaticamente?",
},
UI: {
Confirm: "Confirm",
Cancel: "Cancel",
Close: "Close",
Create: "Create",
Edit: "Edit",
Confirm: "Conferma",
Cancel: "Annulla",
Close: "Chiudi",
Create: "Crea",
Edit: "Modifica",
Export: "Esporta",
Import: "Importa",
Sync: "Sincronizza",
Config: "Configura",
},
Exporter: {
Description: {
Title:
"Solo i messaggi dopo la cancellazione del contesto verranno visualizzati",
},
Model: "Modello",
Messages: "Messaggi",
Topic: "Argomento",
Topic: "Tema",
Time: "Tempo",
},
};

View File

@@ -1,301 +1,564 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const jp: PartialLocaleType = {
WIP: "この機能は開発中です",
Error: {
Unauthorized:
"現在は未承認状態です。左下の設定ボタンをクリックし、アクセスパスワードかOpenAIのAPIキーを入力してください。",
Unauthorized: isApp
? "無効なAPIキーが検出されました。[設定](/#/settings)ページでAPIキーが正しく設定されているか確認してください。"
: "アクセスパスワードが正しくないか空です。[ログイン](/#/auth)ページで正しいアクセスパスワードを入力するか、[設定](/#/settings)ページで自分のOpenAI APIキーを入力してください。",
},
Auth: {
Title: "パスワードが必要です",
Tips: "管理者がパスワード認証を有効にしました。以下にアクセスコードを入力してください",
SubTips: "または、OpenAIまたはGoogle APIキーを入力してください",
Input: "ここにアクセスコードを入力",
Confirm: "確認",
Later: "後で",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 通のチャット`,
ChatItemCount: (count: number) => `${count}件の会話`,
},
Chat: {
SubTitle: (count: number) => `ChatGPTとの ${count} 通のチャット`,
SubTitle: (count: number) => `合計${count}件の会話`,
EditMessage: {
Title: "全てのメッセージを修正",
Title: "メッセージ履歴を編集",
Topic: {
Title: "トピック",
SubTitle: "このトピックを変える",
Title: "チャットテーマ",
SubTitle: "現在のチャットテーマを変更",
},
},
Actions: {
ChatList: "メッセージリストを表示",
CompressedHistory: "圧縮された履歴プロンプトを表示",
ChatList: "メッセージリストを見る",
CompressedHistory: "圧縮された履歴プロンプトを見る",
Export: "チャット履歴をエクスポート",
Copy: "コピー",
Stop: "停止",
Retry: "リトライ",
Pin: "ピン",
PinToastContent:
"コンテキストプロンプトに1つのメッセージをピン留めしました",
PinToastAction: "表示",
Retry: "再試行",
Pin: "固定",
PinToastContent: "1件の会話をプリセットプロンプトに固定しました",
PinToastAction: "見る",
Delete: "削除",
Edit: "編集",
},
Commands: {
new: "新しいチャット",
newm: "マスクから新しいチャット",
next: "次のチャット",
prev: "前のチャット",
clear: "コンテキストをクリア",
del: "チャットを削除",
},
InputActions: {
Stop: "応答を停止",
ToBottom: "最新へスクロール",
Theme: {
auto: "自動テーマ",
light: "ライトモード",
dark: "ダークモード",
},
Prompt: "クイックコマンド",
Masks: "すべてのマスク",
Clear: "チャットをクリア",
Settings: "チャット設定",
UploadImage: "画像をアップロード",
},
Rename: "チャットの名前を変更",
Typing: "入力中…",
Input: (submitKey: string) => {
var inputHints = `${submitKey} で送信`;
var inputHints = `${submitKey}で送信`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += "Shift + Enter で改行";
inputHints += "Shift + Enterで改行";
}
return inputHints + "/ で自動補完をトリガー";
return inputHints + "、/で補完をトリガー、:でコマンドをトリガー";
},
Send: "送信",
Config: {
Reset: "リセット",
SaveAs: "保存",
Reset: "メモリをクリア",
SaveAs: "マスクとして保存",
},
IsContext: "プリセットプロンプト",
},
Export: {
Title: "チャット履歴をMarkdown形式でエクスポート",
Title: "チャット履歴を共有",
Copy: "すべてコピー",
Download: "ファイルをダウンロード",
MessageFromYou: "あなたからのメッセージ",
MessageFromChatGPT: "ChatGPTからのメッセージ",
Share: "ShareGPTに共有",
MessageFromYou: "ユーザー",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "フォーマットをエクスポート",
SubTitle: "マークダウン形式、PNG画像形式を選択できます",
Title: "エクスポート形式",
SubTitle: "MarkdownテキストまたはPNG画像としてエクスポートできます",
},
IncludeContext: {
Title: "コンテキストを含みますか?",
SubTitle: "コンテキストを含ませるか否か",
Title: "マスクコンテキストを含",
SubTitle: "メッセージにマスクコンテキストを表示するかどうか",
},
Steps: {
Select: "エクスポート設定",
Select: "選択",
Preview: "プレビュー",
},
Image: {
Toast: "画像生成中...",
Modal: "長押しまたは右クリックで保存してください。",
Toast: "スクリーンショットを生成中",
Modal: "長押しまたは右クリックして画像を保存",
},
},
Select: {
Search: "検索",
Search: "メッセージを検索",
All: "すべて選択",
Latest: "新しいメッセージを選択",
Clear: "クリア",
Latest: "最新の数件",
Clear: "選択をクリア",
},
Memory: {
Title: "履歴メモリ",
EmptyContent: "まだ記憶されていません",
Send: "メモリを送信",
Copy: "メモリをコピー",
Reset: "チャットをリセット",
ResetConfirm:
"リセット後、現在のチャット履歴と過去のメモリがクリアされます。リセットしてもよろしいですか?",
Title: "履歴の要約",
EmptyContent: "対話内容が短いため、要約は不要です",
Send: "チャット履歴を自動的に圧縮し、コンテキストとして送信",
Copy: "要約をコピー",
Reset: "[unused]",
ResetConfirm: "履歴の要約をリセットしてもよろしいですか?",
},
Home: {
NewChat: "新しいチャット",
DeleteChat: "選択したチャットを削除してもよろしいですか?",
DeleteToast: "チャットが削除されました",
DeleteChat: "選択した会話を削除してもよろしいですか?",
DeleteToast: "会話を削除しました",
Revert: "元に戻す",
},
Settings: {
Title: "設定",
SubTitle: "設定オプション",
SubTitle: "すべての設定オプション",
Danger: {
Reset: {
Title: "設定をリセット",
SubTitle: "すべての設定項目をデフォルトにリセットします",
Action: "今すぐリセットする",
Confirm: "すべての設定項目をリセットしてもよろしいですか?",
Title: "すべての設定をリセット",
SubTitle: "すべての設定項目をデフォルトにリセット",
Action: "今すぐリセット",
Confirm: "すべての設定をリセットしてもよろしいですか?",
},
Clear: {
Title: "データを消去",
SubTitle: "すべてのチャット履歴と設定を消去します",
Action: "今すぐ消去する",
Confirm: "すべてのチャット履歴と設定を消去しますか?",
Title: "すべてのデータをクリア",
SubTitle: "すべてのチャット、設定データをクリア",
Action: "今すぐクリア",
Confirm: "すべてのチャット、設定データをクリアしてもよろしいですか?",
},
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "ての言語",
All: "すべての言語",
},
Avatar: "アバター",
FontSize: {
Title: "フォントサイズ",
SubTitle: "チャット内容のフォントサイズ",
},
InjectSystemPrompts: {
Title: "システムプロンプトの挿入",
FontFamily: {
Title: "チャットフォント",
SubTitle:
"各リクエストのメッセージリストの先頭に、ChatGPTのシステムプロンプトを強制的に追加します",
"チャットコンテンツのフォント、空白の場合はグローバルデフォルトフォントを適用します",
Placeholder: "フォント名",
},
InjectSystemPrompts: {
Title: "システムプロンプトの注入",
SubTitle:
"すべてのリクエストメッセージリストの先頭にChatGPTのシステムプロンプトを強制的に追加",
},
InputTemplate: {
Title: "入力の前処理",
SubTitle: "新規入力がこのテンプレートに埋め込まれます",
Title: "ユーザー入力のプリプロセス",
SubTitle: "最新のメッセージをこのテンプレートに埋め込",
},
Update: {
Version: (x: string) => `現在のバージョン:${x}`,
IsLatest: "最新バージョンです",
CheckUpdate: "アップデートを確認",
IsChecking: "アップデートを確認しています...",
FoundUpdate: (x: string) => `新しいバージョンが見つかりました${x}`,
GoToUpdate: "更新する",
CheckUpdate: "更新を確認",
IsChecking: "更新を確認中...",
FoundUpdate: (x: string) => `新しいバージョンを発見${x}`,
GoToUpdate: "更新へ進む",
},
SendKey: "送信キー",
Theme: "テーマ",
TightBorder: "ボーダーレスモード",
SendPreviewBubble: {
Title: "プレビューバブルの送信",
SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー",
Title: "プレビューバブル",
SubTitle: "プレビューバブルでMarkdownコンテンツをプレビュー",
},
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: {
Splash: {
Title: "キャラクターページ",
SubTitle: "新チャット作成時にキャラクターページを表示する",
Title: "マスク起動画面",
SubTitle: "新しいチャット作成する際にマスク起動画面を表示",
},
Builtin: {
Title: "ビルトインマスクを非表示",
SubTitle: "マスクリストからビルトインを非表示する",
Title: "内蔵マスクを非表示",
SubTitle: "すべてのマスクリストで内蔵マスクを非表示",
},
},
Prompt: {
Disable: {
Title: "プロンプトの自動補完を無効にする",
SubTitle:
"入力フィールドの先頭に / を入力すると、自動補完がトリガーされます。",
Title: "プロンプトの自動補完を無効",
SubTitle: "入力フィールドの先頭に / を入力して自動補完をトリガー",
},
List: "カスタムプロンプトリスト",
ListCount: (builtin: number, custom: number) =>
`組み込み ${builtin} 件、ユーザー定義 ${custom}`,
`内蔵 ${builtin} 件、ユーザー定義 ${custom}`,
Edit: "編集",
Modal: {
Title: "プロンプトリスト",
Add: "新規追加",
Search: "プロンプトワード検索",
Add: "新規作成",
Search: "プロンプト検索",
},
EditModal: {
Title: "編集",
Title: "プロンプトを編集",
},
},
HistoryCount: {
Title: "履歴メッセージ数を添付",
SubTitle: "リクエストごとに添付する履歴メッセージ数",
Title: "履歴メッセージ数",
SubTitle: "リクエストに含まれる履歴メッセージ数",
},
CompressThreshold: {
Title: "履歴メッセージの長さ圧縮しきい値",
SubTitle:
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
Title: "履歴メッセージの圧縮閾値",
SubTitle: "未圧縮の履歴メッセージがこの値を超えた場合、圧縮が行われます",
},
Usage: {
Title: "残高照会",
Title: "残高確認",
SubTitle(used: any, total: any) {
return `今月 $${used} を使用しました。総額は $${total} です。`;
return `今月の使用量 $${used}、サブスクリプション合計 $${total}`;
},
IsChecking: "確認中...",
IsChecking: "確認中",
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)",
Temperature: {
Title: "ランダム性 (temperature)",
SubTitle: "値が大きいほど応答がランダムになります",
},
TopP: {
Title: "トップP (top_p)",
SubTitle:
"値が大きいほど、回答がランダムになります。1以上の値には文字化けが含まれる可能性があります。",
"ランダム性に似ていますが、ランダム性と一緒に変更しないでください",
},
MaxTokens: {
Title: "シングルレスポンス制限 (max_tokens)",
SubTitle: "1回のインタラクションで使用される最大トークン数",
Title: "1回の応答制限 (max_tokens)",
SubTitle: "1回の対話で使用される最大トークン数",
},
PresencePenalty: {
Title: "トピックの新鮮度 (presence_penalty)",
SubTitle: "値が大きいほど新しいトピックへの展開が可能になります",
Title: "新鮮度 (presence_penalty)",
SubTitle: "値が大きいほど新しいトピックに移行する可能性が高くなります",
},
FrequencyPenalty: {
Title: "話題の頻度 (frequency_penalty)",
SubTitle: "値が大きいほど、重複語を低減する可能性が高くなります",
},
AutoGenerateTitle: {
Title: "タイトルの自動生成",
SubTitle: "会話内容に基づいて適切なタイトルを生成する",
Title: "頻度ペナルティ (frequency_penalty)",
SubTitle: "値が大きいほど繰り返しの単語が減少します",
},
},
Store: {
DefaultTopic: "新しいチャット",
BotHello: "何かお手伝いできることはありますか",
Error: "エラーが発生しました。しばらくしてからやり直してください",
BotHello: "何かお手伝いできますか",
Error: "エラーが発生しました。後でもう一度試してください",
Prompt: {
History: (content: string) =>
"これは、AI とユーザの過去のチャット要約した前提となるストーリーです:" +
content,
"これは前提としての履歴チャット要約です:" + content,
Topic:
"45文字でこの文の簡潔な主題を返してください。説明、句読点、感嘆詞、余なテキストは無しで。もし主題がない場合は、「おしゃべり」を返してください",
"この文の簡潔なテーマを四から五文字で返してください。説明、句読点、感嘆詞、余なテキストは不要です。太字も不要です。テーマがない場合は「雑談」と返してください",
Summarize:
"あなたとユーザの会話を簡潔にまとめて、後続のコンテキストプロンプトとして使ってください。200字以内に抑えてください",
"対話の内容を簡潔に要約し、後続のコンテキストプロンプトとして使用します。200字以内に抑えてください",
},
},
Copy: {
Success: "クリップボードに書き込みました",
Failed: "コピーに失敗しました。クリップボード許可を与えてください",
Failed: "コピーに失敗しました。クリップボードの権限を付与してください",
},
Download: {
Success: "内容がダウンロードされました",
Failed: "ダウンロードに失敗しました",
},
Context: {
Toast: (x: any) => `キャラクターが ${x}設定されました`,
Edit: "キャラクタープリセットとモデル設定",
Add: "追加",
Toast: (x: any) => `${x}のプリセットプロンプトが含まれています`,
Edit: "現在の対話設定",
Add: "対話を追加",
Clear: "コンテキストがクリアされました",
Revert: "コンテキストを元に戻す",
},
Plugin: { Name: "プラグイン" },
FineTuned: { Sysmessage: "あなたはアシスタントです" },
Mask: {
Name: "キャラクタープリセット",
Plugin: {
Name: "プラグイン",
},
Discovery: {
Name: "発見",
},
FineTuned: {
Sysmessage: "あなたはアシスタントです",
},
SearchChat: {
Name: "検索",
Page: {
Title: "キャラクタープリセット",
SubTitle: (count: number) => `${count} 件見つかりました。`,
Search: "検索",
Create: "新規",
Title: "チャット履歴を検索",
Search: "検索キーワードを入力",
NoResult: "結果が見つかりませんでした",
NoData: "データがありません",
Loading: "読み込み中",
SubTitle: (count: number) => `${count} 件の結果が見つかりました`,
},
Item: {
Info: (count: number) => `包含 ${count} 条预设对话`,
Chat: "会話",
View: "詳細",
View: "表示",
},
},
Mask: {
Name: "マスク",
Page: {
Title: "プリセットキャラクターマスク",
SubTitle: (count: number) => `${count} 件のプリセットキャラクター定義`,
Search: "キャラクターマスクを検索",
Create: "新規作成",
},
Item: {
Info: (count: number) => `${count} 件のプリセット対話が含まれています`,
Chat: "対話",
View: "表示",
Edit: "編集",
Delete: "削除",
DeleteConfirm: "本当に削除しますか?",
DeleteConfirm: "削除してもよろしいですか?",
},
EditModal: {
Title: (readonly: boolean) =>
`キャラクタープリセット編集 ${readonly ? "(読み取り専用)" : ""}`,
Download: "ダウンロード",
Clone: "複製",
`プリセットマスクの編集 ${readonly ? "(読み取り専用)" : ""}`,
Download: "プリセットをダウンロード",
Clone: "プリセットをクローン",
},
Config: {
Avatar: "キャラクターのアイコン",
Name: "キャラクターの名前",
Avatar: "キャラクターアバター",
Name: "キャラクター",
Sync: {
Title: "グローバル設定を利用する",
SubTitle: "このチャットでグローバル設定を利用します。",
Title: "グローバル設定を使用",
SubTitle: "現在の対話でグローバルモデル設定を使用するかどうか",
Confirm:
"カスタム設定を上書きしてグローバル設定を使用します、よろしいですか?",
"現在の対話のカスタム設定が自動的に上書きされます。グローバル設定を有効にしてもよろしいですか?",
},
HideContext: {
Title: "キャラクター設定を表示しない",
SubTitle: "チャット画面でのキャラクター設定を非表示にします。",
Title: "プリセット対話を非表示",
SubTitle:
"非表示にすると、プリセット対話はチャット画面に表示されません",
},
Share: {
Title: "このマスクを共有",
SubTitle: "このマスクの直リンクを生成",
Action: "リンクをコピー",
},
},
},
NewChat: {
Return: "戻る",
Skip: "スキップ",
Title: "キャラクター",
SubTitle: "さあ、AIにキャラクターを設定して会話を始めてみましょう",
More: "もっと探す",
NotShow: "今後は表示しない",
ConfirmNoShow: "いつでも設定から有効化できます。",
Skip: "直接開始",
NotShow: "今後表示しない",
ConfirmNoShow:
"無効にしてもよろしいですか?無効にした後、設定でいつでも再度有効にできます。",
Title: "マスクを選択",
SubTitle: "今すぐ始めよう、マスクの背後にある魂と思考の衝突",
More: "すべて表示",
},
URLCommand: {
Code: "リンクにアクセスコードが含まれています。自動入力しますか?",
Settings: "リンクにプリセット設定が含まれています。自動入力しますか?",
},
UI: {
Confirm: "確認",
Cancel: "キャンセル",
Close: "閉じる",
Create: "新規",
Create: "新規作成",
Edit: "編集",
Export: "エクスポート",
Import: "インポート",
Sync: "同期",
Config: "設定",
},
Exporter: {
Description: {
Title: "コンテキストをクリアした後のメッセージのみが表示されます",
},
Model: "モデル",
Messages: "メッセージ",
Topic: "トピック",
Topic: "テーマ",
Time: "時間",
},
};

View File

@@ -1,115 +1,258 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const ko: PartialLocaleType = {
WIP: "곧 출시 예정...",
Error: {
Unauthorized:
"권한이 없습니다. 설정 페이지에서 액세스 코드를 [입력하세요](/#/auth).",
Unauthorized: isApp
? "유효하지 않은 API 키가 감지되었습니다. [설정](/#/settings) 페이지에서 API 키가 올바르게 구성되었는지 확인하십시오."
: "잘못된 접근 비밀번호이거나 비밀번호가 비어 있습니다. [로그인](/#/auth) 페이지에서 올바른 접근 비밀번호를 입력하거나 [설정](/#/settings) 페이지에서 OpenAI API 키를 입력하십시오.",
},
Auth: {
Title: "비밀번호 필요",
Tips: "관리자가 비밀번호 인증을 활성화했습니다. 아래에 접근 코드를 입력하십시오.",
SubTips: "또는 OpenAI 또는 Google API 키를 입력하십시오.",
Input: "여기에 접근 코드를 입력하십시오.",
Confirm: "확인",
Later: "나중에 하기",
},
ChatItem: {
ChatItemCount: (count: number) => `${count}개의 메시지`,
ChatItemCount: (count: number) => `${count} 개의 대화`,
},
Chat: {
SubTitle: (count: number) => `ChatGPT와의 ${count}개의 메시지`,
SubTitle: (count: number) => ` ${count} 개의 대화`,
EditMessage: {
Title: "메시지 기록 편집",
Topic: {
Title: "채팅 주제",
SubTitle: "현재 채팅 주제 변경",
},
},
Actions: {
ChatList: "채팅 목록으로 이동",
CompressedHistory: "압축된 기억력 메모리 프롬프트",
Export: "모든 메시지를 Markdown으로 내보내기",
ChatList: "메시지 목록 보기",
CompressedHistory: "압축된 히스토리 프롬프트 보기",
Export: "채팅 기록 내보내기",
Copy: "복사",
Stop: "지",
Stop: "지",
Retry: "다시 시도",
Pin: "고정",
PinToastContent: "1 개의 대화를 프롬프트에 고정했습니다.",
PinToastAction: "보기",
Delete: "삭제",
Edit: "편집",
},
Commands: {
new: "새 채팅",
newm: "마스크에서 새 채팅",
next: "다음 채팅",
prev: "이전 채팅",
clear: "컨텍스트 지우기",
del: "채팅 삭제",
},
InputActions: {
Stop: "응답 중지",
ToBottom: "최신으로 스크롤",
Theme: {
auto: "자동 테마",
light: "라이트 모드",
dark: "다크 모드",
},
Prompt: "빠른 명령",
Masks: "모든 마스크",
Clear: "채팅 지우기",
Settings: "채팅 설정",
UploadImage: "이미지 업로드",
},
Rename: "채팅 이름 변경",
Typing: "입력 중...",
Typing: "입력 중",
Input: (submitKey: string) => {
var inputHints = `${submitKey}를 눌러 보내기`;
var inputHints = `${submitKey} 전송`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter 줄 바꿈";
inputHints += "Shift + Enter 줄 바꿈";
}
return inputHints + ", 프롬프트 검색을 위해 / 입력";
return inputHints + "/ 자동 완성,: 명령어 입력";
},
Send: "보내기",
Send: "전송",
Config: {
Reset: "기본값으로 재설정",
Reset: "기억 지우기",
SaveAs: "마스크로 저장",
},
IsContext: "프롬프트 설정",
},
Export: {
Title: "모든 메시지",
Title: "채팅 기록 공유",
Copy: "모두 복사",
Download: "다운로드",
MessageFromYou: "나의 메시지",
MessageFromChatGPT: "ChatGPT의 메시지",
Download: "파일 다운로드",
Share: "ShareGPT에 공유",
MessageFromYou: "사용자",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "내보내기 형식",
SubTitle: "Markdown 텍스트 또는 PNG 이미지로 내보낼 수 있습니다.",
},
IncludeContext: {
Title: "프롬프트 컨텍스트 포함",
SubTitle: "메시지에 프롬프트 컨텍스트를 표시할지 여부",
},
Steps: {
Select: "선택",
Preview: "미리보기",
},
Image: {
Toast: "스크린샷 생성 중",
Modal: "길게 누르거나 오른쪽 클릭하여 이미지를 저장하십시오.",
},
},
Select: {
Search: "메시지 검색",
All: "모두 선택",
Latest: "최근 몇 개",
Clear: "선택 지우기",
},
Memory: {
Title: "기억 프롬프트",
EmptyContent: "아직 내용이 없습니다.",
Send: "기억 보내기",
Copy: "기억 복사",
Reset: "세션 재설정",
ResetConfirm:
"재설정하면 현재 대화 기록과 기억력이 삭제됩니다. 정말 재설정하시겠습니까?",
Title: "기록 요약",
EmptyContent: "대화 내용이 너무 짧아 요약할 필요 없음",
Send: "자동으로 채팅 기록을 압축하여 컨텍스트로 전송",
Copy: "요약 복사",
Reset: "[사용되지 않음]",
ResetConfirm: "기록 요약을 지우겠습니까?",
},
Home: {
NewChat: "새로운 채팅",
NewChat: "새 채팅",
DeleteChat: "선택한 대화를 삭제하시겠습니까?",
DeleteToast: "채팅이 삭제되었습니다.",
DeleteToast: "대화가 삭제되었습니다.",
Revert: "되돌리기",
},
Settings: {
Title: "설정",
SubTitle: "모든 설정",
SubTitle: "모든 설정 옵션",
Danger: {
Reset: {
Title: "모든 설정 초기화",
SubTitle: "모든 설정 항목을 기본값으로 초기화",
Action: "지금 초기화",
Confirm: "모든 설정을 초기화하시겠습니까?",
},
Clear: {
Title: "모든 데이터 지우기",
SubTitle: "모든 채팅 및 설정 데이터 지우기",
Action: "지금 지우기",
Confirm: "모든 채팅 및 설정 데이터를 지우시겠습니까?",
},
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "All Languages",
Name: "Language", // 주의: 새 번역을 추가하려면 이 값을 번역하지 말고 그대로 유지하세요.
All: "모든 언어",
},
Avatar: "아바타",
FontSize: {
Title: "글꼴 크기",
SubTitle: "채팅 내용의 글꼴 크기 조정",
SubTitle: "채팅 내용의 글꼴 크기",
},
FontFamily: {
Title: "채팅 폰트",
SubTitle: "채팅 내용의 폰트, 비워 두면 글로벌 기본 폰트를 적용",
Placeholder: "폰트 이름",
},
InjectSystemPrompts: {
Title: "시스템 프롬프트 입",
Title: "시스템 수준 프롬프트 입",
SubTitle:
"각 요청 메시지 목록의 시작에 ChatGPT 시스템 프롬프트를 강제로 추가합니다",
"각 요청 메시지 목록의 시작 부분에 ChatGPT 시스템 프롬프트를 강제로 추가",
},
InputTemplate: {
Title: "사용자 입력 전처리",
SubTitle: "사용자의 최신 메시지가 이 템플릿에 채워집니다.",
},
Update: {
Version: (x: string) => `버전: ${x}`,
IsLatest: "최신 버전",
Version: (x: string) => `현재 버전: ${x}`,
IsLatest: "최신 버전입니다.",
CheckUpdate: "업데이트 확인",
IsChecking: "업데이트 확인 중...",
FoundUpdate: (x: string) => `새 버전 발견: ${x}`,
GoToUpdate: "업데이트",
GoToUpdate: "업데이트로 이동",
},
SendKey: "전송 키",
Theme: "테마",
TightBorder: "조밀한 테두리",
TightBorder: "테두리 없는 모드",
SendPreviewBubble: {
Title: "미리 보기 버블 전송",
SubTitle: "버블에서 마크다운 미리 보기",
Title: "미리보기 버블",
SubTitle: "미리보기 버블에서 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: {
Splash: {
Title: "마스크 시작 화면",
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
Title: "마스크 시작 페이지",
SubTitle: "새 채팅 시 마스크 시작 페이지 표시",
},
Builtin: {
Title: "내장 마스크 숨기기",
SubTitle: "모든 마스크 목록에서 내장 마스크 숨기기",
},
},
Prompt: {
Disable: {
Title: "자동 완성 비활성화",
SubTitle: "자동 완성을 활성화하려면 /를 입력하세요.",
Title: "프롬프트 자동 완성 비활성화",
SubTitle: "입력 상자 시작 부분에 / 를 입력하여 자동 완성 활성화",
},
List: "프롬프트 목록",
List: "사용자 정의 프롬프트 목록",
ListCount: (builtin: number, custom: number) =>
`내장 ${builtin}개, 사용자 정의 ${custom}`,
`내장 ${builtin} 개, 사용자 정의 ${custom} `,
Edit: "편집",
Modal: {
Title: "프롬프트 목록",
Add: "추가",
Add: "새로 만들기",
Search: "프롬프트 검색",
},
EditModal: {
@@ -117,116 +260,293 @@ const ko: PartialLocaleType = {
},
},
HistoryCount: {
Title: "첨부된 메시지 수",
SubTitle: "요청당 첨부된 전송된 메시지 수",
Title: "히스토리 메시지 수",
SubTitle: "각 요청에 포함된 히스토리 메시지 수",
},
CompressThreshold: {
Title: "기록 압축 임계값",
SubTitle: "압축 메시지 길이가 임계값을 초과하면 압축",
Title: "히스토리 메시지 길이 압축 임계값",
SubTitle: "압축되지 않은 히스토리 메시지가 이 값을 초과하면 압축 수행",
},
Usage: {
Title: "계정 잔액",
Title: "잔액 조회",
SubTitle(used: any, total: any) {
return `이번 달 사용${used}, 구독 ${total}`;
return `이번 달 사용된 금액: $${used},총 구독 금액: $${total}`;
},
IsChecking: "확인 중...",
Check: "확인",
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
Check: "다시 확인",
NoAccess: "잔액을 보려면 API 키 또는 접근 비밀번호를 입력하십시오.",
},
Model: "모델",
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: "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: {
Title: "온도 (temperature)",
SubTitle: "값이 클수록 더 무작위한 출력이 생성됩니다.",
Title: "무작위성 (temperature)",
SubTitle: "값이 클수록 응답이 더 무작위",
},
TopP: {
Title: "탑 P 샘플링 (top_p)",
SubTitle: "무작위성과 유사하지만, 무작위성과 함께 변경하지 마십시오.",
},
MaxTokens: {
Title: "최대 토큰 수 (max_tokens)",
SubTitle: "입력 토큰과 생성된 토큰의 최대 길이",
Title: "단일 응답 제한 (max_tokens)",
SubTitle: "단일 상호작용에 사용되는 최대 토큰 수",
},
PresencePenalty: {
Title: "존재 페널티 (presence_penalty)",
SubTitle: "값이 클수록 새로운 주제에 대해 대화할 가능성이 높아집니다.",
Title: "주제 신선도 (presence_penalty)",
SubTitle: "값이 클수록 새로운 주제로 확장할 가능성이 높",
},
FrequencyPenalty: {
Title: "빈도 페널티(frequency penalty)",
SubTitle: "값이 클수록 같은 줄이 반복될 가능성이 줄어듭니다.",
Title: "빈도 벌점 (frequency_penalty)",
SubTitle: "값이 클수록 중복 단어 감소 가능성 높음",
},
},
Store: {
DefaultTopic: "새 대화",
BotHello: "안녕하세요! 오늘 도움이 필요하신가요?",
Error: "문제가 발생했습니다. 나중에 다시 시도해주세요.",
DefaultTopic: "새 채팅",
BotHello: "무엇을 도와드릴까요?",
Error: "오류가 발생했습니다. 나중에 다시 시도해 주세요.",
Prompt: {
History: (content: string) =>
"이것은 AI와 사용자 간의 대화 기록을 요약한 내용입니다: " + content,
History: (content: string) => "이것은 이전 채팅 요약입니다: " + content,
Topic:
"다음과 같이 대화 내용을 요약하는 4~5단어 제목을 생성해주세요. 따옴표, 구두점, 인용부호, 기호 또는 추가 텍스트를 제거하십시오. 따옴표로 감싸진 부분을 제거하십시오.",
"네 글자에서 다섯 글자로 이 문장의 간략한 주제를 반환하세요. 설명이나 문장 부호, 어미, 불필요한 텍스트, 굵은 글씨는 필요 없습니다. 주제가 없다면 '잡담'이라고만 반환하세요.",
Summarize:
"200단어 이내로 저희 토론을 간략히 요약하여 앞으로의 맥락으로 사용할 수 있는 프롬프트로 만들어주세요.",
"대화 내용을 간략히 요약하여 후속 컨텍스트 프롬프트로 사용하세요. 200자 이내로 작성하세요.",
},
},
Copy: {
Success: "클립보드에 복사되었습니다.",
Failed: "복사 실패, 클립보드 접근 권한을 허용해주세요.",
Failed: "복사 실패, 클립보드 권한을 부여해주세요.",
},
Download: {
Success: "내용이 디렉토리에 다운로드되었습니다.",
Failed: "다운로드 실패.",
},
Context: {
Toast: (x: any) => `컨텍스트 프롬프트 ${x}개 사용`,
Edit: "컨텍스트 및 메모리 프롬프트",
Add: "프롬프트 추가",
Toast: (x: any) => ` ${x} 개의 프리셋 프롬프트 포함됨`,
Edit: "현재 대화 설정",
Add: "대화 추가",
Clear: "컨텍스트가 지워졌습니다.",
Revert: "컨텍스트 복원",
},
Plugin: {
Name: "플러그인",
},
FineTuned: {
Sysmessage: "당신은 어시스턴트입니다",
Sysmessage: "당신은 보조자입니다.",
},
SearchChat: {
Name: "검색",
Page: {
Title: "채팅 기록 검색",
Search: "검색어 입력",
NoResult: "결과를 찾을 수 없습니다",
NoData: "데이터가 없습니다",
Loading: "로딩 중",
SubTitle: (count: number) => `${count}개의 결과를 찾았습니다`,
},
Item: {
View: "보기",
},
},
Mask: {
Name: "마스크",
Page: {
Title: "프롬프트 템플릿",
SubTitle: (count: number) => `${count}개의 프롬프트 템플릿`,
Search: "템플릿 검색",
Create: "생성",
Title: "프리셋 캐릭터 마스크",
SubTitle: (count: number) => `${count} 개의 프리셋 캐릭터 정의`,
Search: "캐릭터 마스크 검색",
Create: "새로 만들기",
},
Item: {
Info: (count: number) => `${count}개의 프롬프롬프트`,
Chat: "채팅",
Info: (count: number) => ` ${count} 개의 프리셋 대화 포함`,
Chat: "대화",
View: "보기",
Edit: "편집",
Delete: "삭제",
DeleteConfirm: "삭제하시겠습니까?",
DeleteConfirm: "삭제를 확인하시겠습니까?",
},
EditModal: {
Title: (readonly: boolean) =>
`롬프트 템플릿 편집 ${readonly ? "(읽기 전용)" : ""}`,
Download: "다운로드",
Clone: "복제",
`리셋 마스크 편집 ${readonly ? "읽기 전용" : ""}`,
Download: "프리셋 다운로드",
Clone: "프리셋 복제",
},
Config: {
Avatar: " 아바타",
Name: " 이름",
Avatar: "캐릭터 아바타",
Name: "캐릭터 이름",
Sync: {
Title: "전역 설정 사용",
SubTitle: "현재 대화가 전역 모델 설정을 사용하는지 여부",
Confirm:
"현재 대화의 사용자 정의 설정이 자동으로 덮어쓰여질 것입니다. 전역 설정을 활성화하시겠습니까?",
},
HideContext: {
Title: "프리셋 대화 숨기기",
SubTitle: "숨기면 프리셋 대화가 채팅 화면에 나타나지 않습니다.",
},
Share: {
Title: "이 마스크 공유하기",
SubTitle: "이 마스크의 직접 링크 생성",
Action: "링크 복사",
},
},
},
NewChat: {
Return: "돌아가기",
Skip: "건너뛰기",
Title: "마스크 선택",
SubTitle: "마스크 뒤의 영혼과 대화하세요",
More: "더 보기",
NotShow: "다시 표시하지 않음",
Skip: "바로 시작",
NotShow: "다시 보지 않기",
ConfirmNoShow:
"비활성화하시겠습니까? 나중에 설정에서 다시 활성화할 수 있습니다.",
"비활성화하시겠습니까? 비활성화 후 언제든지 설정에서 다시 활성화할 수 있습니다.",
Title: "마스크 선택",
SubTitle: "지금 시작하여 마스크 뒤의 사고와 교류해보세요.",
More: "모두 보기",
},
URLCommand: {
Code: "링크에 액세스 코드가 포함되어 있습니다. 자동으로 입력하시겠습니까?",
Settings:
"링크에 프리셋 설정이 포함되어 있습니다. 자동으로 입력하시겠습니까?",
},
UI: {
Confirm: "확인",
Cancel: "취소",
Close: "닫기",
Create: "생성",
Create: "새로 만들기",
Edit: "편집",
Export: "내보내기",
Import: "가져오기",
Sync: "동기화",
Config: "구성",
},
Exporter: {
Description: {
Title: "컨텍스트가 지워진 후의 메시지만 표시됩니다.",
},
Model: "모델",
Messages: "메시지",
Topic: "주제",

View File

@@ -1,158 +1,572 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const no: PartialLocaleType = {
WIP: "Arbeid pågår ...",
Error: {
Unauthorized:
"Du har ikke tilgang. [Vennlig oppgi tildelt adgangskode](/#/auth).",
Unauthorized: isApp
? "Ugyldig API-nøkkel oppdaget. Gå til [innstillinger](/#/settings) for å sjekke om API-nøkkelen er riktig konfigurert."
: "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: {
ChatItemCount: (count: number) => `${count} meldinger`,
ChatItemCount: (count: number) => `${count} samtaler`,
},
Chat: {
SubTitle: (count: number) => `${count} meldinger med ChatGPT`,
SubTitle: (count: number) => `Totalt ${count} samtaler`,
EditMessage: {
Title: "Rediger meldingshistorikk",
Topic: {
Title: "Samtaleemne",
SubTitle: "Endre nåværende samtaleemne",
},
},
Actions: {
ChatList: "Gå til chatlisten",
CompressedHistory: "Komprimert historikk for instrukser",
Export: "Eksporter alle meldinger i markdown-format",
ChatList: "Vis meldingsliste",
CompressedHistory: "Vis komprimert historikk",
Export: "Eksporter samtaler",
Copy: "Kopier",
Stop: "Stopp",
Retry: "Prøv igjen",
Pin: "Fest",
PinToastContent:
"1 samtale er festet til forhåndsdefinerte oppfordringer",
PinToastAction: "Se",
Delete: "Slett",
Edit: "Rediger",
},
Rename: "Gi nytt navn",
Typing: "Skriver …",
Commands: {
new: "Ny samtale",
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) => {
var inputHints = `${submitKey} for å sende`;
var inputHints = `${submitKey} send`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter for å omgi";
inputHints += "Shift + Enter for linjeskift";
}
return inputHints + ", / for å søke instrukser";
return (
inputHints + "/ for å utløse autoutfylling, : for å utløse kommando"
);
},
Send: "Send",
Config: {
Reset: "Fjern minne",
SaveAs: "Lagre som maske",
},
IsContext: "Forhåndsdefinerte oppfordringer",
},
Export: {
Title: "Alle meldinger",
Copy: "Kopiere alle",
Download: "Last ned",
MessageFromYou: "Melding fra deg",
MessageFromChatGPT: "Melding fra ChatGPT",
Title: "Del samtalehistorikk",
Copy: "Kopier alt",
Download: "Last ned fil",
Share: "Del til ShareGPT",
MessageFromYou: "Bruker",
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: {
Title: "Minneinstruks",
EmptyContent: "Ingen sålant.",
Send: "Send minne",
Copy: "Kopiere minne",
Reset: "Nulstill sesjon",
ResetConfirm:
"Om du nillstiller vil du slette hele historikken. Er du sikker på at du vil nullstille?",
Title: "Historisk sammendrag",
EmptyContent: "Samtaleinnholdet er for kort, ingen oppsummering nødvendig",
Send: "Automatisk komprimere samtalehistorikk og sende som kontekst",
Copy: "Kopier sammendrag",
Reset: "[unused]",
ResetConfirm: "Er du sikker på at du vil tømme historisk sammendrag?",
},
Home: {
NewChat: "Ny chat",
DeleteChat: "Bekreft for å slette det valgte dialogen",
NewChat: "Ny samtale",
DeleteChat: "Er du sikker på at du vil slette den valgte samtalen?",
DeleteToast: "Samtale slettet",
Revert: "Tilbakestill",
Revert: "Angre",
},
Settings: {
Title: "Innstillinger",
SubTitle: "Alle innstillinger",
SubTitle: "Alle innstillingsalternativer",
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: {
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: "Avatar",
Avatar: "Profilbilde",
FontSize: {
Title: "Fontstørrelsen",
SubTitle: "Juster fontstørrelsen for samtaleinnholdet.",
Title: "Skriftstørrelse",
SubTitle: "Skriftstørrelse for samtaleinnhold",
},
FontFamily: {
Title: "Chat-skrifttype",
SubTitle:
"Skrifttypen for chatinnhold, la stå tom for å bruke global standardskrifttype",
Placeholder: "Skriftnavn",
},
InjectSystemPrompts: {
Title: "Sett inn systemprompter",
Title: "Injiser systemprompter",
SubTitle:
"Tving tillegg av en simulert ChatGPT-systemprompt i begynnelsen av meldingslisten for hver forespørsel",
"Tving inn et systemprompt som simulerer ChatGPT i starten av hver forespørsel",
},
InputTemplate: {
Title: "Forhåndsbehandling av brukerinput",
SubTitle:
"Den nyeste meldingen fra brukeren vil bli fylt ut i denne malen",
},
Update: {
Version: (x: string) => `Versjon: ${x}`,
IsLatest: "Siste versjon",
CheckUpdate: "Se etter oppdatering",
IsChecking: "Ser etter oppdatering ...",
FoundUpdate: (x: string) => `Fant ny versjon: ${x}`,
GoToUpdate: "Oppdater",
Version: (x: string) => `Nåværende versjon: ${x}`,
IsLatest: "Er den nyeste versjonen",
CheckUpdate: "Sjekk oppdateringer",
IsChecking: "Sjekker oppdateringer...",
FoundUpdate: (x: string) => `Ny versjon oppdaget: ${x}`,
GoToUpdate: "Gå til oppdatering",
},
SendKey: "Send nøkkel",
SendKey: "Send-knapp",
Theme: "Tema",
TightBorder: "Stram innramming",
TightBorder: "Ingen ramme-modus",
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: {
Disable: {
Title: "Skru av autofullfør",
SubTitle: "Skriv / for å trigge autofullfør",
Title: "Deaktiver automatisk fullføring av oppfordringer",
SubTitle: "Skriv / i tekstboksen for å utløse automatisk fullføring",
},
List: "Instruksliste",
List: "Egendefinerte oppfordringer",
ListCount: (builtin: number, custom: number) =>
`${builtin} innebygde, ${custom} brukerdefinerte`,
Edit: "Endre",
`Innebygde ${builtin}, brukerdedefinerte ${custom}`,
Edit: "Rediger",
Modal: {
Title: "Instruksliste",
Add: "Legg til",
Search: "Søk instrukser",
Title: "Oppfordringsliste",
Add: "Ny",
Search: "Søk oppfordringer",
},
EditModal: {
Title: "Rediger oppfordring",
},
},
HistoryCount: {
Title: "Tall på tilhørende meldinger",
SubTitle: "Antall sendte meldinger tilknyttet hver spørring",
Title: "Antall historiske meldinger",
SubTitle: "Antall historiske meldinger som sendes med hver forespørsel",
},
CompressThreshold: {
Title: "Terskeverdi for komprimering av historikk",
Title: "Kompressterskel for historiske meldinger",
SubTitle:
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
"Når ukomprimerte historiske meldinger overskrider denne verdien, vil de bli komprimert",
},
Usage: {
Title: "Saldo for konto",
Title: "Saldoforespørsel",
SubTitle(used: any, total: any) {
return `Brukt denne måneden $${used}, abonnement $${total}`;
return `Brukt denne måneden $${used}, total abonnementsbeløp $${total}`;
},
IsChecking: "Sjekker ...",
Check: "Sjekk",
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
IsChecking: "Sjekker...",
Check: "Sjekk på nytt",
NoAccess: "Skriv inn API-nøkkel eller tilgangspassord for å se saldo",
},
Model: "Model",
Access: {
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: {
Title: "Temperatur",
SubTitle: "Høyere verdi gir mer kreative svar",
Title: "Tilfeldighet (temperature)",
SubTitle: "Høyere verdi gir mer tilfeldige svar",
},
TopP: {
Title: "Kjerneprøvetaking (top_p)",
SubTitle:
"Ligner på tilfeldighet, men endre ikke sammen med tilfeldighet",
},
MaxTokens: {
Title: "Maks tokens",
SubTitle: "Maksimum lengde på tokens for instrukser og svar",
Title: "Maksimalt antall tokens per svar (max_tokens)",
SubTitle: "Maksimalt antall tokens brukt i en enkelt interaksjon",
},
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: {
DefaultTopic: "Ny samtale",
BotHello: "Hei! Hva kan jeg hjelpe deg med i dag?",
Error: "Noe gikk galt, vennligst prøv igjen senere.",
BotHello: "Hva kan jeg hjelpe deg med?",
Error: "Noe gikk galt, prøv igjen senere",
Prompt: {
History: (content: string) =>
"Dette er et sammendrag av chatthistorikken mellom AI-en og brukeren som en oppsummering: " +
"Dette er oppsummeringen av historiske samtaler som bakgrunn:" +
content,
Topic:
"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.",
'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".',
Summarize:
"Oppsummer diskusjonen vår kort i 200 ord eller mindre for å bruke som en oppfordring til fremtidig sammenheng.",
"Oppsummer samtalen kort som en kontekstuell prompt for fremtidige referanser, hold det innen 200 tegn",
},
},
Copy: {
Success: "Kopiert til utklippstavle",
Failed: "Kopiering feilet. Vennligst gi tilgang til utklippstavlen.",
Success: "Kopiert til utklippstavlen",
Failed: "Kopiering mislyktes, vennligst gi tillatelse til utklippstavlen",
},
Download: {
Success: "Innholdet er lastet ned til katalogen din.",
Failed: "Nedlasting mislyktes.",
},
Context: {
Toast: (x: any) => `Med ${x} kontekstuelle instrukser`,
Edit: "Kontekstuelle -og minneinstrukser",
Add: "Legg til",
Toast: (x: any) => `Inneholder ${x} forhåndsinnstilte oppfordringer`,
Edit: "Nåværende samtaleinnstillinger",
Add: "Legg til en ny samtale",
Clear: "Konteksten er tømt",
Revert: "Gjenopprett konteksten",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Du er en assistent",
},
SearchChat: {
Name: "Søk",
Page: {
Title: "Søk i chatthistorikk",
Search: "Skriv inn søkeord",
NoResult: "Ingen resultater funnet",
NoData: "Ingen data",
Loading: "Laster inn",
SubTitle: (count: number) => `Fant ${count} resultater`,
},
Item: {
View: "Vis",
},
},
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: {
Model: "Model",
Messages: "Meldingar",
Description: {
Title: "Bare meldinger etter at konteksten er ryddet vil bli vist",
},
Model: "Modell",
Messages: "Meldinger",
Topic: "Emne",
Time: "Tid",
},

View File

@@ -153,6 +153,12 @@ const pt: PartialLocaleType = {
Title: "Tamanho da Fonte",
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: {
Title: "Inserir Prompts de Sistema",
SubTitle: "Inserir um prompt de sistema global para cada requisição",
@@ -399,6 +405,21 @@ const pt: PartialLocaleType = {
FineTuned: {
Sysmessage: "Você é um assistente que",
},
SearchChat: {
Name: "Pesquisar",
Page: {
Title: "Pesquisar histórico de chat",
Search: "Digite palavras-chave para pesquisa",
NoResult: "Nenhum resultado encontrado",
NoData: "Sem dados",
Loading: "Carregando",
SubTitle: (count: number) => `Encontrado ${count} resultados`,
},
Item: {
View: "Ver",
},
},
Mask: {
Name: "Máscara",
Page: {

View File

@@ -1,229 +1,552 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { PartialLocaleType } from "../locales/index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const ru: PartialLocaleType = {
WIP: "Скоро...",
Error: {
Unauthorized:
"Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
Unauthorized: isApp
? "Обнаружен недействительный API-ключ. Пожалуйста, перейдите на страницу [Настройки](/#/settings), чтобы проверить правильность конфигурации API-ключа."
: "Неверный или пустой пароль доступа. Пожалуйста, перейдите на страницу [Вход](/#/auth), чтобы ввести правильный пароль доступа, или на страницу [Настройки](/#/settings), чтобы ввести ваш собственный API-ключ OpenAI.",
},
Auth: {
Title: "Требуется пароль",
Tips: "Администратор включил проверку пароля. Пожалуйста, введите код доступа ниже",
SubTips: "Или введите ваш API-ключ OpenAI или Google",
Input: "Введите код доступа здесь",
Confirm: "Подтвердить",
Later: "Позже",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} сообщений`,
ChatItemCount: (count: number) => `${count} бесед`,
},
Chat: {
SubTitle: (count: number) => `${count} сообщений с ChatGPT`,
SubTitle: (count: number) => `Всего ${count} бесед`,
EditMessage: {
Title: "Редактировать сообщение",
Topic: {
Title: "Тема чата",
SubTitle: "Изменить текущую тему чата",
},
},
Actions: {
ChatList: ерейти к списку чатов",
CompressedHistory: "Сжатая история памяти",
Export: "Экспортировать все сообщения в формате Markdown",
ChatList: росмотреть список сообщений",
CompressedHistory: "Просмотреть сжатую историю подсказок",
Export: "Экспортировать чат",
Copy: "Копировать",
Stop: "Остановить",
Retry: "Повторить",
Pin: "Закрепить",
PinToastContent: "1 беседа закреплена в предустановленных подсказках",
PinToastAction: "Просмотреть",
Delete: "Удалить",
Edit: "Редактировать",
},
Commands: {
new: "Новый чат",
newm: "Создать чат из маски",
next: "Следующий чат",
prev: "Предыдущий чат",
clear: "Очистить контекст",
del: "Удалить чат",
},
InputActions: {
Stop: "Остановить ответ",
ToBottom: "Перейти к последнему",
Theme: {
auto: "Автоматическая тема",
light: "Светлая тема",
dark: "Темная тема",
},
Prompt: "Быстрая команда",
Masks: "Все маски",
Clear: "Очистить чат",
Settings: "Настройки чата",
UploadImage: "Загрузить изображение",
},
Rename: "Переименовать чат",
Typing: "Печатает…",
Input: (submitKey: string) => {
var inputHints = `${submitKey} для отправки сообщения`;
var inputHints = `${submitKey} Отправить`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter для переноса строки";
inputHints += "Shift + Enter для новой строки";
}
return inputHints + ", / для поиска подсказок";
return inputHints + "/ для автозаполнения,: для команд";
},
Send: "Отправить",
Config: {
Reset: "Сбросить настройки",
Reset: "Очистить память",
SaveAs: "Сохранить как маску",
},
IsContext: "Предустановленные подсказки",
},
Export: {
Title: "Все сообщения",
Title: "Поделиться историей чата",
Copy: "Копировать все",
Download: "Скачать",
MessageFromYou: "Сообщение от вас",
MessageFromChatGPT: "Сообщение от ChatGPT",
Download: "Скачать файл",
Share: "Поделиться в ShareGPT",
MessageFromYou: "Пользователь",
MessageFromChatGPT: "ChatGPT",
Format: {
Title: "Формат экспорта",
SubTitle: "Можно экспортировать как Markdown текст или PNG изображение",
},
IncludeContext: {
Title: "Включить контекст маски",
SubTitle: "Показывать ли контекст маски в сообщениях",
},
Steps: {
Select: "Выбрать",
Preview: "Предпросмотр",
},
Image: {
Toast: "Создание скриншота",
Modal: "Длительное нажатие или правый клик для сохранения изображения",
},
},
Select: {
Search: "Поиск сообщений",
All: "Выбрать все",
Latest: "Последние сообщения",
Clear: "Очистить выбор",
},
Memory: {
Title: "Память",
EmptyContent: "Пусто.",
Send: "Отправить память",
Copy: "Копировать память",
Reset: "Сбросить сессию",
ResetConfirm:
"При сбросе текущая история переписки и историческая память будут удалены. Вы уверены, что хотите сбросить?",
Title: "Историческое резюме",
EmptyContent: "Содержимое чата слишком короткое, чтобы суммировать",
Send: "Автоматически сжать историю чата и отправить как контекст",
Copy: "Копировать резюме",
Reset: "[не используется]",
ResetConfirm: "Подтвердить очистку исторического резюме?",
},
Home: {
NewChat: "Новый чат",
DeleteChat: "Вы действительно хотите удалить выбранный разговор?",
DeleteToast: "Чат удален",
Revert: "Отмена",
DeleteChat: "Подтвердить удаление выбранного чата?",
DeleteToast: "Беседа удалена",
Revert: "Отменить",
},
Settings: {
Title: "Настройки",
SubTitle: "Все настройки",
SubTitle: "Все параметры настроек",
Danger: {
Reset: {
Title: "Сброс всех настроек",
SubTitle: "Сброс всех параметров до значений по умолчанию",
Action: "Сбросить сейчас",
Confirm: "Подтвердить сброс всех настроек?",
},
Clear: {
Title: "Очистить все данные",
SubTitle: "Очистить все чаты и данные настроек",
Action: "Очистить сейчас",
Confirm: "Подтвердить очистку всех чатов и данных настроек?",
},
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
Name: "Language", // ATTENTION: если вы хотите добавить новый перевод, не переводите это значение, оставьте его как `Language`
All: "Все языки",
},
Avatar: "Аватар",
FontSize: {
Title: "Размер шрифта",
SubTitle: "Настроить размер шрифта контента чата",
SubTitle: "Размер шрифта в чате",
},
FontFamily: {
Title: "Шрифт чата",
SubTitle:
"Шрифт содержимого чата, оставьте пустым для применения глобального шрифта по умолчанию",
Placeholder: "Название шрифта",
},
InjectSystemPrompts: {
Title: "Вставить системные подсказки",
SubTitle:
"Принудительно добавить симулированную системную подсказку ChatGPT в начало списка сообщений для каждого запроса",
"Принудительно добавлять системную подсказку, имитирующую ChatGPT, в начале каждого запроса",
},
InputTemplate: {
Title: "Предварительная обработка пользовательского ввода",
SubTitle:
"Последнее сообщение пользователя будет подставлено в этот шаблон",
},
Update: {
Version: (x: string) => `Версия: ${x}`,
IsLatest: "Последняя версия",
CheckUpdate: "Проверить обновление",
IsChecking: "Проверка обновления...",
FoundUpdate: (x: string) => `Найдена новая версия: ${x}`,
GoToUpdate: "Обновить",
Version: (x: string) => `Текущая версия: ${x}`,
IsLatest: "Установлена последняя версия",
CheckUpdate: "Проверить обновления",
IsChecking: "Проверка обновлений...",
FoundUpdate: (x: string) => `Найдено новое обновление: ${x}`,
GoToUpdate: "Перейти к обновлению",
},
SendKey: "Клавиша отправки",
SendKey: "Кнопка отправки",
Theme: "Тема",
TightBorder: "Узкая граница",
TightBorder: "Режим без границ",
SendPreviewBubble: {
Title: "Отправить предпросмотр",
SubTitle: "Предварительный просмотр markdown в пузыре",
Title: "Предварительный просмотр пузырьков",
SubTitle:
"Просмотр содержимого 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: {
Splash: {
Title: "Экран заставки маски",
SubTitle: "Показывать экран заставки маски перед началом нового чата",
Title: "Стартовая страница масок",
SubTitle:
"При создании нового чата отображать стартовую страницу масок",
},
Builtin: {
Title: "Скрыть встроенные маски",
SubTitle: "Скрыть встроенные маски во всех списках масок",
},
},
Prompt: {
Disable: {
Title: "Отключить автозаполнение",
SubTitle: "Ввод / для запуска автозаполнения",
Title: "Отключить автозаполнение подсказок",
SubTitle: "Введите / в начале строки для активации автозаполнения",
},
List: "Список подсказок",
List: "Список пользовательских подсказок",
ListCount: (builtin: number, custom: number) =>
`${builtin} встроенных, ${custom} пользовательских`,
`Встроенные ${builtin}, пользовательские ${custom}`,
Edit: "Редактировать",
Modal: {
Title: "Список подсказок",
Add: "Добавить",
Add: "Создать",
Search: "Поиск подсказок",
},
EditModal: {
Title: "Редактировать подсказку",
Title: "Редактировать подсказки",
},
},
HistoryCount: {
Title: "Количество прикрепляемых сообщений",
SubTitle:
"Количество отправляемых сообщений, прикрепляемых к каждому запросу",
Title: "Количество истории сообщений",
SubTitle: "Количество историй сообщений, отправляемых с каждым запросом",
},
CompressThreshold: {
Title: "Порог сжатия истории",
Title: "Порог сжатия длины истории сообщений",
SubTitle:
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
"Когда не сжатая история сообщений превышает это значение, происходит сжатие",
},
Usage: {
Title: "Баланс аккаунта",
Title: "Проверка баланса",
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: "Модель",
Temperature: {
Title: "Температура",
SubTitle: "Чем выше значение, тем более случайный вывод",
Title: "Случайность (temperature)",
SubTitle: "Чем больше значение, тем более случайные ответы",
},
TopP: {
Title: "Ядро выборки (top_p)",
SubTitle: "Похожие на случайность, но не изменяйте вместе с случайностью",
},
MaxTokens: {
Title: "Максимальное количество токенов",
SubTitle: "Максимальная длина вводных и генерируемых токенов",
Title: "Ограничение на количество токенов за один раз (max_tokens)",
SubTitle: "Максимальное количество токенов на одно взаимодействие",
},
PresencePenalty: {
Title: "Штраф за повторения",
Title: "Наказание за новизну тем (presence_penalty)",
SubTitle:
"Чем выше значение, тем больше вероятность общения на новые темы",
"Чем больше значение, тем выше вероятность расширения на новые темы",
},
FrequencyPenalty: {
Title: "Штраф за частоту",
Title: "Наказание за частоту (frequency_penalty)",
SubTitle:
"Большее значение снижает вероятность повторения одной и той же строки",
"Чем больше значение, тем выше вероятность уменьшения повторяющихся слов",
},
},
Store: {
DefaultTopic: "Новый разговор",
BotHello: "Здравствуйте! Как я могу вам помочь сегодня?",
Error: "Что-то пошло не так. Пожалуйста, попробуйте еще раз позже.",
DefaultTopic: "Новый чат",
BotHello: "Чем могу помочь?",
Error: "Произошла ошибка, попробуйте позже",
Prompt: {
History: (content: string) =>
"Это краткое содержание истории чата между ИИ и пользователем: " +
content,
"Это резюме истории чата как предыстория: " + content,
Topic:
"Пожалуйста, создайте заголовок из четырех или пяти слов, который кратко описывает нашу беседу, без введения, знаков пунктуации, кавычек, точек, символов или дополнительного текста. Удалите кавычки.",
"Укажите краткую тему этого сообщения в четырех-пяти словах, без объяснений, знаков препинания, междометий, лишнего текста или выделения. Если темы нет, просто напишите 'Болтовня'",
Summarize:
"Кратко изложите нашу дискуссию в 200 словах или менее для использования в будущем контексте.",
"Кратко подведите итоги содержимого беседы для использования в качестве последующего контекстного запроса, не более 200 слов",
},
},
Copy: {
Success: "Скопировано в буфер обмена",
Failed:
"Не удалось скопировать, пожалуйста, предоставьте разрешение на доступ к буферу обмена",
Failed: "Не удалось скопировать, предоставьте доступ к буферу обмена",
},
Download: {
Success: "Содержимое успешно загружено в вашу директорию.",
Failed: "Не удалось загрузить.",
},
Context: {
Toast: (x: any) => `С ${x} контекстными подсказками`,
Edit: "Контекстные и памятные подсказки",
Add: "Добавить подсказку",
Toast: (x: any) => `Содержит ${x} предустановленных подсказок`,
Edit: "Текущие настройки чата",
Add: "Добавить новый чат",
Clear: "Контекст очищен",
Revert: "Восстановить контекст",
},
Plugin: {
Name: "Плагин",
Name: "Плагины",
},
FineTuned: {
Sysmessage: "Вы - ассистент, который",
Sysmessage: "Вы - помощник",
},
SearchChat: {
Name: "Поиск",
Page: {
Title: "Поиск в истории чатов",
Search: "Введите ключевые слова для поиска",
NoResult: "Результатов не найдено",
NoData: "Нет данных",
Loading: "Загрузка",
SubTitle: (count: number) => `Найдено ${count} результатов`,
},
Item: {
View: "Просмотр",
},
},
Mask: {
Name: "Маска",
Page: {
Title: "Шаблон подсказки",
SubTitle: (count: number) => `${count} шаблонов подсказок`,
Search: "Поиск шаблонов",
Title: "Предустановленные роли",
SubTitle: (count: number) => `${count} предустановленных ролей`,
Search: "Поиск ролей",
Create: "Создать",
},
Item: {
Info: (count: number) => `${count} подсказок`,
Chat: "Чат",
View: "Просмотр",
Info: (count: number) => `Содержит ${count} предустановленных диалогов`,
Chat: "Диалог",
View: "Просмотреть",
Edit: "Редактировать",
Delete: "Удалить",
DeleteConfirm: "Подтвердить удаление?",
},
EditModal: {
Title: (readonly: boolean) =>
`Редактирование шаблона подсказки ${
readonly ? "(только для чтения)" : ""
`Редактирование предустановленной маски ${
readonly ? "только для чтения" : ""
}`,
Download: "Скачать",
Clone: "Клонировать",
Download: "Скачать предустановку",
Clone: "Клонировать предустановку",
},
Config: {
Avatar: "Аватар бота",
Name: "Имя бота",
Avatar: "Аватар роли",
Name: "Название роли",
Sync: {
Title: "Использовать глобальные настройки",
SubTitle:
"Будет ли текущий чат использовать глобальные настройки модели",
Confirm:
"Пользовательские настройки текущего чата будут автоматически заменены, подтвердите активацию глобальных настроек?",
},
HideContext: {
Title: "Скрыть предустановленные диалоги",
SubTitle:
"После скрытия предустановленные диалоги не будут отображаться в чате",
},
Share: {
Title: "Поделиться этой маской",
SubTitle: "Создать прямую ссылку на эту маску",
Action: "Скопировать ссылку",
},
},
},
NewChat: {
Return: "Вернуться",
Skip: "Пропустить",
Title: "Выберите маску",
SubTitle: "Общайтесь с душой за маской",
More: "Найти еще",
NotShow: "Не показывать снова",
Skip: "Начать сразу",
NotShow: "Больше не показывать",
ConfirmNoShow:
"Подтвердите отключение? Вы можете включить это позже в настройках.",
"Подтвердить отключение? После отключения вы всегда сможете включить его снова в настройках.",
Title: "Выберите маску",
SubTitle: "Начните сейчас, столкнитесь с мыслями за маской",
More: "Показать все",
},
URLCommand: {
Code: "Обнаружен код доступа в ссылке, автоматически заполнить?",
Settings:
"Обнаружены предустановленные настройки в ссылке, автоматически заполнить?",
},
UI: {
@@ -232,8 +555,15 @@ const ru: PartialLocaleType = {
Close: "Закрыть",
Create: "Создать",
Edit: "Редактировать",
Export: "Экспортировать",
Import: "Импортировать",
Sync: "Синхронизировать",
Config: "Настройки",
},
Exporter: {
Description: {
Title: "Только сообщения после очистки контекста будут отображаться",
},
Model: "Модель",
Messages: "Сообщения",
Topic: "Тема",

View File

@@ -155,6 +155,12 @@ const sk: PartialLocaleType = {
Title: "Veľkosť písma",
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: {
Title: "Vložiť systémové výzvy",
SubTitle: "Vložiť globálnu systémovú výzvu pre každú požiadavku",
@@ -417,6 +423,21 @@ const sk: PartialLocaleType = {
FineTuned: {
Sysmessage: "Ste asistent, ktorý",
},
SearchChat: {
Name: "Hľadať",
Page: {
Title: "Hľadať v histórii chatu",
Search: "Zadajte kľúčové slová na vyhľadávanie",
NoResult: "Nenašli sa žiadne výsledky",
NoData: "Žiadne údaje",
Loading: "Načítava sa",
SubTitle: (count: number) => `Nájdených ${count} výsledkov`,
},
Item: {
View: "Zobraziť",
},
},
Mask: {
Name: "Maska",
Page: {

View File

@@ -1,236 +1,567 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const tr: PartialLocaleType = {
WIP: "Çalışma devam ediyor...",
Error: {
Unauthorized:
"Yetkisiz erişim, lütfen erişim kodunu ayarlar [sayfasından](/#/auth) giriniz.",
Unauthorized: isApp
? "Geçersiz API Anahtarı tespit edildi, lütfen API Anahtarını doğru şekilde yapılandırmak için [Ayarlar](/#/settings) sayfasına gidin."
: "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: {
ChatItemCount: (count: number) => `${count} mesaj`,
ChatItemCount: (count: number) => `${count} konuşma`,
},
Chat: {
SubTitle: (count: number) => `ChatGPT tarafından ${count} mesaj`,
SubTitle: (count: number) => `Toplam ${count} konuşma`,
EditMessage: {
Title: "Mesaj Kayıtlarını Düzenle",
Topic: {
Title: "Sohbet Konusu",
SubTitle: "Geçerli sohbet konusunu değiştir",
},
},
Actions: {
ChatList: "Sohbet Listesine Git",
CompressedHistory: "Sıkıştırılmış Geçmiş Bellek Komutu",
Export: "Tüm Mesajları Markdown Olarak Dışa Aktar",
ChatList: "Mesaj listesine bak",
CompressedHistory: "Sıkıştırılmış geçmişi gör",
Export: "Sohbet kayıtlarını dışa aktar",
Copy: "Kopyala",
Stop: "Durdur",
Retry: "Tekrar Dene",
Delete: "Delete",
Retry: "Yeniden dene",
Pin: "Sabitlenmiş",
PinToastContent: "1 konuşma varsayılan ifadeye sabitlendi",
PinToastAction: "Görünüm",
Delete: "Sil",
Edit: "Düzenle",
},
Rename: "Sohbeti Yeniden Adlandır",
Commands: {
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…",
Input: (submitKey: string) => {
var inputHints = `Göndermek için ${submitKey}`;
var inputHints = `${submitKey} gönder`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", kaydırmak için Shift + Enter";
inputHints += " Shift + Enter satır sonu için";
}
return inputHints + ", komutları aramak için / (eğik çizgi)";
return inputHints + " / tamamlama için, : komutlar için";
},
Send: "Gönder",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
Reset: "Hafızayı temizle",
SaveAs: "Maske olarak kaydet",
},
IsContext: "Varsayılan ifade",
},
Export: {
Title: "Tüm Mesajlar",
Copy: "Tümünü Kopyala",
Download: "İndir",
MessageFromYou: "Sizin Mesajınız",
MessageFromChatGPT: "ChatGPT'nin Mesajı",
Title: "Sohbet kayıtlarını paylaş",
Copy: "Hepsini kopyala",
Download: "Dosyayı indir",
Share: "ShareGPT'ye paylaş",
MessageFromYou: "Kullanıcı",
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: {
Title: "Bellek Komutları",
EmptyContent: "Henüz değil.",
Send: "Belleği Gönder",
Copy: "Belleği Kopyala",
Reset: "Oturumu Sıfırla",
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?",
Title: "Geçmiş Özeti",
EmptyContent: "Sohbet içeriği çok kısa, özetleme gerek yok",
Send: "Sohbet kayıtlarını otomatik olarak sıkıştır ve bağlam olarak gönder",
Copy: "Özeti kopyala",
Reset: "[kullanılmadı]",
ResetConfirm: "Geçmiş özetini temizlemek istediğinize emin misiniz?",
},
Home: {
NewChat: "Yeni Sohbet",
DeleteChat: "Seçili sohbeti silmeyi onaylıyor musunuz?",
DeleteToast: "Sohbet Silindi",
Revert: "Geri Al",
NewChat: "Yeni sohbet",
DeleteChat: "Seçilen sohbeti silmek istediğinize emin misiniz?",
DeleteToast: "Sohbet silindi",
Revert: "Geri al",
},
Settings: {
Title: "Ayarlar",
SubTitle: "Tüm Ayarlar",
SubTitle: "Tüm ayar seçenekleri",
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Tüm Diller",
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?",
},
},
Avatar: "Avatar",
Lang: {
Name: "Language", // Dikkat: yeni bir çeviri eklemek isterseniz, bu değeri çevirmeyin, `Language` olarak bırakın
All: "Tüm diller",
},
Avatar: "Profil Resmi",
FontSize: {
Title: "Yazı Boyutu",
SubTitle: "Sohbet içeriğinin yazı boyutunu ayarlayın",
SubTitle: "Sohbet içeriğinin yazı boyutu",
},
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: {
Title: "Sistem İpucu Ekleyin",
SubTitle:
"Her istek için ileti listesinin başına simüle edilmiş bir ChatGPT sistem ipucu ekleyin",
Title: "Sistem Seviyesi İpucu Enjeksiyonu",
SubTitle: "Her isteğin başına ChatGPT benzeri bir sistem ipucu ekle",
},
InputTemplate: {
Title: "Kullanıcı Girdisi Ön İşleme",
SubTitle: "Kullanıcının en son mesajı bu şablona doldurulur",
},
Update: {
Version: (x: string) => `Sürüm: ${x}`,
Version: (x: string) => `Mevcut sürüm: ${x}`,
IsLatest: "En son sürüm",
CheckUpdate: "Güncellemeyi Kontrol Et",
IsChecking: "Güncelleme kontrol ediliyor...",
CheckUpdate: "Güncellemeleri kontrol et",
IsChecking: "Güncellemeler kontrol ediliyor...",
FoundUpdate: (x: string) => `Yeni sürüm bulundu: ${x}`,
GoToUpdate: "Güncelle",
GoToUpdate: "Güncellemeye git",
},
SendKey: "Gönder Tuşu",
SendKey: "Gönderme Tuşu",
Theme: "Tema",
TightBorder: "Tam Ekran",
TightBorder: "Sınır Yok Modu",
SendPreviewBubble: {
Title: "Mesaj Önizleme Balonu",
SubTitle: "Preview markdown in bubble",
Title: "Önizleme Balonu",
SubTitle: "Markdown içeriğini önizleme balonunda görüntüle",
},
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: {
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Title: "Maske Başlangıç Sayfası",
SubTitle:
"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: {
Disable: {
Title: "Otomatik tamamlamayı devre dışı bırak",
SubTitle: "Otomatik tamamlamayı kullanmak için / (eğik çizgi) girin",
Title: "İpucu Tamamlamayı Devre Dışı Bırak",
SubTitle:
"Giriş kutusunun başına / yazarak otomatik tamamlamayı tetikle",
},
List: "Komut Listesi",
List: "Özelleştirilmiş İpucu Listesi",
ListCount: (builtin: number, custom: number) =>
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
`Yerleşik ${builtin} tane, kullanıcı tanımlı ${custom} tane`,
Edit: "Düzenle",
Modal: {
Title: "Prompt List",
Add: "Add One",
Search: "Search Prompts",
Title: "İpucu Listesi",
Add: "Yeni Ekle",
Search: "İpucu Ara",
},
EditModal: {
Title: "Edit Prompt",
Title: "İpucu Düzenle",
},
},
HistoryCount: {
Title: "Ekli Mesaj Sayısı",
SubTitle: "İstek başına ekli gönderilen mesaj sayısı",
Title: "Ekli Geçmiş Mesaj Sayısı",
SubTitle: "Her istekte taşınan geçmiş mesaj sayısı",
},
CompressThreshold: {
Title: "Geçmiş Sıkıştırma Eşiği",
Title: "Geçmiş Mesaj Uzunluğu Sıkıştırma Eşiği",
SubTitle:
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
"Sıkıştırılmamış geçmiş mesaj bu değeri aştığında sıkıştırma yapılır",
},
Usage: {
Title: "Hesap Bakiyesi",
Title: "Bakiye Sorgulama",
SubTitle(used: any, total: any) {
return `Bu ay kullanılan $${used}, abonelik $${total}`;
return `Bu ay kullanılan $${used}, toplam abonelik ücreti $${total}`;
},
IsChecking: "Kontrol ediliyor...",
Check: "Tekrar Kontrol Et",
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
IsChecking: "Kontrol ediliyor",
Check: "Yeniden kontrol et",
NoAccess:
"Bakiye görüntülemek için API Anahtarı veya erişim şifresi girin",
},
Model: "Model",
Access: {
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: {
Title: "Gerçeklik",
Title: "Rastgelelik (temperature)",
SubTitle: "Değer arttıkça yanıt daha rastgele olur",
},
TopP: {
Title: "Nükleer Örnekleme (top_p)",
SubTitle:
"Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
"Rastgeleliğe benzer, ancak rastgelelik ile birlikte değiştirmeyin",
},
MaxTokens: {
Title: "Maksimum Belirteç",
SubTitle:
"Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
Title: "Tek Yanıt Limiti (max_tokens)",
SubTitle: "Tek etkileşimde kullanılan maksimum Token sayısı",
},
PresencePenalty: {
Title: "Varlık Cezası",
SubTitle:
"Daha büyük bir değer, yeni konular hakkında konuşma olasılığını artırır",
Title: "Konu Tazeliği (presence_penalty)",
SubTitle: "Değer arttıkça, yeni konulara geçiş olasılığı artar",
},
FrequencyPenalty: {
Title: "Frekans Cezası",
Title: "Frekans Cezası (frequency_penalty)",
SubTitle:
"Aynı satırı tekrar etme olasılığını azaltan daha büyük bir değer",
"Değer arttıkça, tekrar eden kelimelerin azalması olasılığı artar",
},
},
Store: {
DefaultTopic: "Yeni Konuşma",
BotHello: "Merhaba! Size bugün nasıl yardımcı olabilirim?",
Error: "Bir şeyler yanlış gitti. Lütfen daha sonra tekrar deneyiniz.",
DefaultTopic: "Yeni Sohbet",
BotHello: "Size nasıl yardımcı olabilirim?",
Error: "Bir hata oluştu, lütfen daha sonra tekrar deneyin",
Prompt: {
History: (content: string) =>
"Bu, yapay zeka ile kullanıcı arasındaki sohbet geçmişinin bir özetidir: " +
content,
History: (content: string) => "Bu, geçmiş sohbetin özeti: " + content,
Topic:
"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.",
"Bu cümlenin dört ila beş kelimelik kısa başlığını doğrudan verin, ı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.",
Summarize:
"Gelecekteki bağlam için bir bilgi istemi olarak kullanmak üzere tartışmamızı en fazla 200 kelimeyle özetleyin.",
"Sohbet içeriğini kısaca özetleyin, bu özet sonraki bağlam ipucu olarak kullanılacaktır, 200 kelime içinde tutun",
},
},
Copy: {
Success: "Panoya kopyalandı",
Failed: "Kopyalama başarısız oldu, lütfen panoya erişim izni verin",
Success: "Panoya yazıldı",
Failed: "Kopyalama başarısız, lütfen panoya erişim izni verin",
},
Download: {
Success: "İçerik dizininize indirildi.",
Failed: "İndirme başarısız.",
},
Context: {
Toast: (x: any) => `${x} bağlamsal bellek komutu`,
Edit: "Bağlamsal ve Bellek Komutları",
Add: "Yeni Ekle",
Toast: (x: any) => `${x} tane önceden tanımlı ipucu içeriyor`,
Edit: "Mevcut sohbet ayarları",
Add: "Yeni bir sohbet ekle",
Clear: "Bağlam temizlendi",
Revert: "Bağlamı geri getir",
},
Plugin: {
Name: "Plugin",
Name: "Eklenti",
},
FineTuned: {
Sysmessage: "Sen bir asistansın",
},
Mask: {
Name: "Mask",
SearchChat: {
Name: "Ara",
Page: {
Title: "Prompt Template",
SubTitle: (count: number) => `${count} prompt templates`,
Search: "Search Templates",
Create: "Create",
Title: "Sohbet geçmişini ara",
Search: "Arama anahtar kelimelerini girin",
NoResult: "Sonuç bulunamadı",
NoData: "Veri yok",
Loading: "Yükleniyor",
SubTitle: (count: number) => `${count} sonuç bulundu`,
},
Item: {
Info: (count: number) => `${count} prompts`,
Chat: "Chat",
View: "View",
Edit: "Edit",
Delete: "Delete",
DeleteConfirm: "Confirm to delete?",
View: "Görüntüle",
},
},
Mask: {
Name: "Maske",
Page: {
Title: "Önceden Tanımlı Karakter Maskeleri",
SubTitle: (count: number) =>
`${count} tane önceden tanımlı karakter tanımı`,
Search: "Karakter maskesi ara",
Create: "Yeni oluştur",
},
Item: {
Info: (count: number) => `${count} tane önceden tanımlı sohbet içeriyor`,
Chat: "Sohbet",
View: "Görüntüle",
Edit: "Düzenle",
Delete: "Sil",
DeleteConfirm: "Silmek istediğinizden emin misiniz?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Prompt Template ${readonly ? "(readonly)" : ""}`,
Download: "Download",
Clone: "Clone",
`Önceden Tanımlı Maskeyi Düzenle ${readonly ? " (Salt Okunur)" : ""}`,
Download: "Önceden Tanımlı Maskeyi İndir",
Clone: "Önceden Tanımlı Maskeyi Kopyala",
},
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Avatar: "Karakter Profil Resmi",
Name: "Karakter Adı",
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: {
Return: "Return",
Skip: "Skip",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Not Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
Return: "Geri dön",
Skip: "Doğrudan başla",
NotShow: "Bir daha gösterme",
ConfirmNoShow:
"Devre dışı bırakmak istediğinizden emin misiniz? Devre dışı bıraktıktan sonra ayarlardan tekrar etkinleştirebilirsiniz.",
Title: "Bir Maske Seçin",
SubTitle:
"Ş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: {
Confirm: "Confirm",
Cancel: "Cancel",
Close: "Close",
Create: "Create",
Edit: "Edit",
Confirm: "Onayla",
Cancel: "İptal et",
Close: "Kapat",
Create: "Yeni oluştur",
Edit: "Düzenle",
Export: "Dışa Aktar",
Import: "İçe Aktar",
Sync: "Senkronize et",
Config: "Yapılandır",
},
Exporter: {
Description: {
Title: "Sadece bağlam temizlendikten sonraki mesajlar gösterilecektir",
},
Model: "Model",
Messages: "Mesajlar",
Topic: "Konu",

View File

@@ -153,6 +153,11 @@ const tw = {
Title: "字型大小",
SubTitle: "聊天內容的字型大小",
},
FontFamily: {
Title: "聊天字體",
SubTitle: "聊天內容的字體,若置空則應用全局默認字體",
Placeholder: "字體名稱",
},
InjectSystemPrompts: {
Title: "匯入系統提示",
SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示",
@@ -241,7 +246,7 @@ const tw = {
},
List: "自訂提示詞列表",
ListCount: (builtin: number, custom: number) =>
`內建 ${builtin} 條,使用者自訂 ${custom}`,
`內建 ${builtin} 條,使用者自訂 ${custom}`,
Edit: "編輯",
Modal: {
Title: "提示詞列表",
@@ -447,6 +452,21 @@ const tw = {
},
},
},
SearchChat: {
Name: "搜索",
Page: {
Title: "搜索聊天記錄",
Search: "輸入搜索關鍵詞",
NoResult: "沒有找到結果",
NoData: "沒有數據",
Loading: "加載中",
SubTitle: (count: number) => `找到 ${count} 條結果`,
},
Item: {
View: "查看",
},
},
NewChat: {
Return: "返回",
Skip: "跳過",

View File

@@ -1,179 +1,464 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
import { getClientConfig } from "../config/client";
const isApp = !!getClientConfig()?.isApp;
const vi: PartialLocaleType = {
WIP: "Sắp ra mắt...",
Error: {
Unauthorized:
"Truy cập chưa xác thực, vui lòng nhập mã truy cập trong trang cài đặt.",
Unauthorized: isApp
? "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."
: "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: {
ChatItemCount: (count: number) => `${count} tin nhắn`,
ChatItemCount: (count: number) => `${count} cuộc trò chuyện`,
},
Chat: {
SubTitle: (count: number) => `${count} tin nhắn với ChatGPT`,
Actions: {
ChatList: "Xem danh sách chat",
CompressedHistory: "Nén tin nhắn trong quá khứ",
Export: "Xuất tất cả tin nhắn dưới dạng Markdown",
Copy: "Sao chép",
Stop: "Dừng",
Retry: "Thử lại",
Delete: "Xóa",
SubTitle: (count: number) => `Tổng cộng ${count} cuộc trò chuyện`,
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",
},
},
Rename: "Đổi tên",
Actions: {
ChatList: "Xem danh sách tin nhắn",
CompressedHistory: "Xem lịch sử Prompt đã nén",
Export: "Xuất khẩu ghi chép trò chuyện",
Copy: "Sao chép",
Stop: "Dừng 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",
Edit: "Chỉnh sửa",
},
Commands: {
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…",
Input: (submitKey: string) => {
var inputHints = `${submitKey} để gửi`;
var inputHints = `${submitKey} gửi`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter để xuống dòng";
inputHints += "Shift + Enter xuống dòng";
}
return inputHints + ", / để tìm kiếm mẫu gợi ý";
return inputHints + "/ kích hoạt hoàn chỉnh, : kích hoạt lệnh";
},
Send: "Gửi",
Config: {
Reset: "Khôi phục cài đặt gốc",
SaveAs: "Lưu dưới dạng Mẫu",
Reset: "Xóa trí nhớ",
SaveAs: "Lưu dưới dạng mặt nạ",
},
IsContext: "Lời nhắc đã đặt sẵn",
},
Export: {
Title: "Tất cả tin nhắn",
Title: "Chia sẻ ghi chép trò chuyện",
Copy: "Sao chép tất cả",
Download: "Tải xuống",
MessageFromYou: "Tin nhắn của bạn",
MessageFromChatGPT: "Tin nhắn từ ChatGPT",
Download: "Tải xuống tệp",
Share: "Chia sẻ lên ShareGPT",
MessageFromYou: "Người dùng",
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: {
Title: "Lịch sử tin nhắn",
EmptyContent: "Chưa có tin nhắn",
Send: "Gửi tin nhắn trong quá khứ",
Copy: "Sao chép tin nhắn trong quá khứ",
Reset: "Đặt lại phiên",
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?",
Title: "Tóm tắt lịch sử",
EmptyContent: "Nội dung trò chuyện quá ngắn, không cần tóm tắt",
Send: "Tự động nén ghi chép trò chuyện và gửi dưới dạng ngữ cảnh",
Copy: "Sao chép tóm tắt",
Reset: "[unused]",
ResetConfirm: "Xác nhận xóa tóm tắt lịch sử?",
},
Home: {
NewChat: "Cuộc trò chuyện mới",
DeleteChat: "Xác nhận xóa các cuộc trò chuyện đã chọn?",
DeleteChat: "Xác nhận xóa cuộc trò chuyện đã chọn?",
DeleteToast: "Đã xóa cuộc trò chuyện",
Revert: "Khôi phục",
Revert: "Hoàn tác",
},
Settings: {
Title: "Cài đặt",
SubTitle: "Tất cả cài đặt",
SubTitle: "Tất cả các tùy chọn 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: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
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`
All: "Tất cả ngôn ngữ",
},
Avatar: "nh đại diện",
Avatar: "nh đại diện",
FontSize: {
Title: "Font chữ",
SubTitle: "Thay đổi font chữ của nội dung trò chuyện",
Title: "Kích thước chữ",
SubTitle: "Kích thước 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: {
Title: "Tiêm Prompt Hệ thống",
Title: "Tiêm thông báo hệ thống",
SubTitle:
"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",
"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",
},
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: {
Version: (x: string) => `Phiên bản: ${x}`,
IsLatest: "Phiên bản mới nhất",
CheckUpdate: "Kiểm tra bản cập nhật",
IsChecking: "Kiểm tra bản cập nhật...",
FoundUpdate: (x: string) => `Phát hiện phiên bản mới: ${x}`,
GoToUpdate: "Cập nhật",
Version: (x: string) => `Phiên bản hiện tại: ${x}`,
IsLatest: "Đã là phiên bản mới nhất",
CheckUpdate: "Kiểm tra cập nhật",
IsChecking: "Đang kiểm tra cập nhật...",
FoundUpdate: (x: string) => `Tìm thấy phiên bản mới: ${x}`,
GoToUpdate: "Đi đến cập nhật",
},
SendKey: "Phím gửi",
Theme: "Theme",
Theme: "Giao diện",
TightBorder: "Chế độ không viền",
SendPreviewBubble: {
Title: "Gửi bong bóng xem trước",
SubTitle: "Xem trước nội dung markdown bằng bong bóng",
Title: "Bong bóng xem trước",
SubTitle: "Xem nội dung Markdown trong bong bóng xem trước",
},
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: {
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
Title: "Trang khởi động mặt nạ",
SubTitle: "Hiển thị trang khởi động mặt nạ khi tạo 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: {
Disable: {
Title: "Vô hiệu hóa chức năng tự động hoàn thành",
SubTitle: "Nhập / để kích hoạt chức năng tự động hoàn thành",
Title: "Vô hiệu hóa tự động hoàn thành lệnh",
SubTitle: "Nhập / ở đầu ô nhập để kích hoạt tự động hoàn thành",
},
List: "Danh sách mẫu gợi ý",
List: "Danh sách lệnh tùy chỉnh",
ListCount: (builtin: number, custom: number) =>
`${builtin} có sẵn, ${custom} do người dùng xác định`,
`Tích hợp ${builtin} mục, người dùng định nghĩa ${custom} mục`,
Edit: "Chỉnh sửa",
Modal: {
Title: "Danh sách mẫu gợi ý",
Add: "Thêm",
Search: "Tìm kiếm mẫu",
Title: "Danh sách lệnh",
Add: "Tạo mới",
Search: "Tìm kiếm lệnh",
},
EditModal: {
Title: "Chỉnh sửa mẫu",
Title: "Chỉnh sửa lệnh",
},
},
HistoryCount: {
Title: "Số lượng tin nhắn đính kèm",
SubTitle: "Số lượng tin nhắn trong quá khứ được gửi kèm theo mỗi yêu cầu",
Title: "Số tin nhắn lịch sử kèm theo",
SubTitle: "Số tin nhắn lịch sử kèm theo mỗi yêu cầu",
},
CompressThreshold: {
Title: "Ngưỡng nén lịch sử tin nhắn",
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
Title: "Ngưỡng nén tin nhắn lịch sử",
SubTitle:
"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: {
Title: "Hạn mức tài khoản",
Title: "Tra cứu số dư",
SubTitle(used: any, total: any) {
return `Đã sử dụng $${used} trong tháng này, hạn mức $${total}`;
return `Đã sử dụng trong tháng: $${used}, Tổng số đăng ký: $${total}`;
},
IsChecking: "Đang kiểm tra...",
Check: "Kiểm tra",
NoAccess: "Nhập API Key để kiểm tra hạn mức",
IsChecking: "Đang kiểm tra",
Check: "Kiểm tra lại",
NoAccess: "Nhập khóa API hoặc mật khẩu truy cập để xem số dư",
},
Model: "Mô hình",
Access: {
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: {
Title: "Tính ngẫu nhiên (temperature)",
Title: "Độ ngẫu nhiên (temperature)",
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: {
Title: "Giới hạn số lượng token (max_tokens)",
SubTitle: "Số lượng token tối đa được sử dụng trong mỗi lần tương tác",
Title: "Giới hạn phản hồi (max_tokens)",
SubTitle: "Số Token tối đa cho mỗi tương tác",
},
PresencePenalty: {
Title: "Chủ đề mới (presence_penalty)",
SubTitle: "Giá trị càng lớn tăng khả năng mở rộng sang các chủ đề mới",
Title: "Độ mới của chủ đề (presence_penalty)",
SubTitle:
"Giá trị càng lớn, khả năng mở rộng đến các chủ đề mới càng cao",
},
FrequencyPenalty: {
Title: "Hình phạt tần suất",
SubTitle: "Giá trị lớn hơn làm giảm khả năng lặp lại cùng một dòng",
Title: "Hình phạt tần suất (frequency_penalty)",
SubTitle: "Giá trị càng lớn, khả năng giảm từ ng lặp lại càng cao",
},
},
Store: {
DefaultTopic: "Cuộc trò chuyện mới",
BotHello: "Xin chào! Mình có thể giúp gì cho bạn?",
Error: "Có lỗi xảy ra, vui lòng thử lại sau.",
DefaultTopic: "Trò chuyện mới",
BotHello: "Có thể giúp gì cho bạn?",
Error: "Đã xảy ra lỗi, vui lòng thử lại sau",
Prompt: {
History: (content: string) =>
"Tóm tắt ngắn gọn cuộc trò chuyện giữa người dùng và AI: " + content,
"Đây là tóm tắt cuộc trò chuyện lịch sử như tiền đề: " + content,
Topic:
"Sử dụng 4 đến 5 từ tóm tắt cuộc trò chuyện này 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.",
'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"',
Summarize:
"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ử dng làm gợi ý cho ngữ cảnh tiếp theo.",
"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ừ",
},
},
Copy: {
Success: "Sao chép vào bộ nhớ tạm",
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",
Success: "Đã sao chép vào clipboard",
Failed: "Sao chép thất bại, vui lòng cấp quyền clipboard",
},
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: {
Toast: (x: any) => `Sử dụng ${x} tin nhắn chứa ngữ cảnh`,
Edit: "Thiết lập ngữ cảnh và bộ nhớ",
Add: "Thêm tin nhắn",
Toast: (x: any) => `Bao gồm ${x} lệnh gợi ý đã định sẵn`,
Edit: "Cài đặt cuộc trò chuyện hiện tại",
Add: "Thêm một cuộc trò chuyện",
Clear: "Ngữ cảnh đã được xóa",
Revert: "Khôi phục ngữ cảnh",
},
Plugin: {
Name: "Plugin",
@@ -181,53 +466,99 @@ const vi: PartialLocaleType = {
FineTuned: {
Sysmessage: "Bạn là một trợ lý",
},
Mask: {
Name: "Mẫu",
SearchChat: {
Name: "Tìm kiếm",
Page: {
Title: "Mẫu trò chuyện",
SubTitle: (count: number) => `${count} mẫu`,
Search: "Tìm kiếm mẫu",
Create: "Tạo",
Title: "Tìm kiếm lịch sử trò chuyện",
Search: "Nhập từ khóa tìm kiếm",
NoResult: "Không tìm thấy kết quả",
NoData: "Không có dữ liệu",
Loading: "Đang tải",
SubTitle: (count: number) => `Tìm thấy ${count} kết quả`,
},
Item: {
Info: (count: number) => `${count} tin nhắn`,
Chat: "Chat",
View: "Xem trước",
View: "Xem",
},
},
Mask: {
Name: "Mặt nạ",
Page: {
Title: "Mặt nạ vai trò đã định sẵn",
SubTitle: (count: number) => `${count} định nghĩa vai trò đã định sẵn`,
Search: "Tìm kiếm mặt nạ vai trò",
Create: "Tạo mới",
},
Item: {
Info: (count: number) => `Bao gồm ${count} cuộc trò chuyện đã định sẵn`,
Chat: "Trò chuyện",
View: "Xem",
Edit: "Chỉnh sửa",
Delete: "Xóa",
DeleteConfirm: "Xác nhận xóa?",
},
EditModal: {
Title: (readonly: boolean) =>
`Chỉnh sửa mẫu ${readonly ? "(chỉ xem)" : ""}`,
Download: "Tải xuống",
Clone: "Tạo bản sao",
`Chỉnh sửa mặt nạ định sẵn ${readonly ? "(chỉ đọc)" : ""}`,
Download: "Tải xuống mặt nạ",
Clone: "Nhân bản mặt nạ",
},
Config: {
Avatar: "nh đại diện bot",
Name: "Tên bot",
Avatar: "nh đại diện vai trò",
Name: "Tên vai trò",
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: {
Return: "Quay lại",
Skip: "Bỏ qua",
Title: "Chọn 1 biểu tượng",
SubTitle: "Bắt đầu trò chuyện ẩn sau lớp mặt nạ",
More: "Tìm thêm",
NotShow: "Không hiển thị lại",
ConfirmNoShow: "Xác nhận tắt? Bạn có thể bật lại trong phần cài đặt.",
Return: "Trở lại",
Skip: "Bắt đầu ngay",
NotShow: "Không hiển thị nữa",
ConfirmNoShow:
"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.",
Title: "Chọn một mặt nạ",
SubTitle: "Bắt đầu ngay, va chạm với suy nghĩ của linh hồn đứng sau mặt nạ",
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: {
Confirm: "Xác nhận",
Cancel: "Hủy",
Close: "Đóng",
Create: "Tạo",
Create: "Tạo mới",
Edit: "Chỉnh sửa",
Export: "Xuất",
Import: "Nhập",
Sync: "Đồng bộ",
Config: "Cấu hình",
},
Exporter: {
Description: {
Title: "Chỉ tin nhắn sau khi xóa ngữ cảnh mới được hiển thị",
},
Model: "Mô hình",
Messages: "Thông điệp",
Messages: "Tin nhắn",
Topic: "Chủ đề",
Time: "Thời gian",
},

View File

@@ -1,6 +1,7 @@
import {
ApiPath,
DEFAULT_API_HOST,
GoogleSafetySettingsThreshold,
ServiceProvider,
StoreKey,
} from "../constant";
@@ -38,7 +39,21 @@ const DEFAULT_ALIBABA_URL = isApp
? DEFAULT_API_HOST + "/api/proxy/alibaba"
: ApiPath.Alibaba;
console.log("DEFAULT_ANTHROPIC_URL", DEFAULT_ANTHROPIC_URL);
const DEFAULT_TENCENT_URL = isApp
? DEFAULT_API_HOST + "/api/proxy/tencent"
: ApiPath.Tencent;
const DEFAULT_MOONSHOT_URL = isApp
? DEFAULT_API_HOST + "/api/proxy/moonshot"
: ApiPath.Moonshot;
const DEFAULT_STABILITY_URL = isApp
? DEFAULT_API_HOST + "/api/proxy/stability"
: ApiPath.Stability;
const DEFAULT_IFLYTEK_URL = isApp
? DEFAULT_API_HOST + "/api/proxy/iflytek"
: ApiPath.Iflytek;
const DEFAULT_ACCESS_STATE = {
accessCode: "",
@@ -59,6 +74,7 @@ const DEFAULT_ACCESS_STATE = {
googleUrl: DEFAULT_GOOGLE_URL,
googleApiKey: "",
googleApiVersion: "v1",
googleSafetySettings: GoogleSafetySettingsThreshold.BLOCK_ONLY_HIGH,
// anthropic
anthropicUrl: DEFAULT_ANTHROPIC_URL,
@@ -78,6 +94,24 @@ const DEFAULT_ACCESS_STATE = {
alibabaUrl: DEFAULT_ALIBABA_URL,
alibabaApiKey: "",
// moonshot
moonshotUrl: DEFAULT_MOONSHOT_URL,
moonshotApiKey: "",
//stability
stabilityUrl: DEFAULT_STABILITY_URL,
stabilityApiKey: "",
// tencent
tencentUrl: DEFAULT_TENCENT_URL,
tencentSecretKey: "",
tencentSecretId: "",
// iflytek
iflytekUrl: DEFAULT_IFLYTEK_URL,
iflytekApiKey: "",
iflytekApiSecret: "",
// server config
needCode: true,
hideUserApiKey: false,
@@ -126,6 +160,17 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["alibabaApiKey"]);
},
isValidTencent() {
return ensure(get(), ["tencentSecretKey", "tencentSecretId"]);
},
isValidMoonshot() {
return ensure(get(), ["moonshotApiKey"]);
},
isValidIflytek() {
return ensure(get(), ["iflytekApiKey"]);
},
isAuthorized() {
this.fetch();
@@ -138,6 +183,9 @@ export const useAccessStore = createPersistStore(
this.isValidBaidu() ||
this.isValidByteDance() ||
this.isValidAlibaba() ||
this.isValidTencent() ||
this.isValidMoonshot() ||
this.isValidIflytek() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);

View File

@@ -9,8 +9,6 @@ import {
DEFAULT_MODELS,
DEFAULT_SYSTEM_TEMPLATE,
KnowledgeCutOffDate,
ServiceProvider,
ModelProvider,
StoreKey,
SUMMARIZE_MODEL,
GEMINI_SUMMARIZE_MODEL,
@@ -28,6 +26,20 @@ import { nanoid } from "nanoid";
import { createPersistStore } from "../utils/store";
import { collectModelsWithDefaultModel } from "../utils/model";
import { useAccessStore } from "./access";
import { isDalle3 } from "../utils";
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
export type ChatMessageTool = {
id: string;
index?: number;
type?: string;
function?: {
name: string;
arguments?: string;
};
content?: string;
isError?: boolean;
};
export type ChatMessage = RequestMessage & {
date: string;
@@ -35,6 +47,7 @@ export type ChatMessage = RequestMessage & {
isError?: boolean;
id: string;
model?: ModelType;
tools?: ChatMessageTool[];
};
export function createMessage(override: Partial<ChatMessage>): ChatMessage {
@@ -92,7 +105,7 @@ function createEmptySession(): ChatSession {
}
function getSummarizeModel(currentModel: string) {
// if it is using gpt-* models, force to use 3.5 to summarize
// if it is using gpt-* models, force to use 4o-mini to summarize
if (currentModel.startsWith("gpt")) {
const configStore = useAppConfig.getState();
const accessStore = useAccessStore.getState();
@@ -390,8 +403,24 @@ export const useChatStore = createPersistStore(
}
ChatControllerPool.remove(session.id, botMessage.id);
},
onBeforeTool(tool: ChatMessageTool) {
(botMessage.tools = botMessage?.tools || []).push(tool);
get().updateCurrentSession((session) => {
session.messages = session.messages.concat();
});
},
onAfterTool(tool: ChatMessageTool) {
botMessage?.tools?.forEach((t, i, tools) => {
if (tool.id == t.id) {
tools[i] = { ...tool };
}
});
get().updateCurrentSession((session) => {
session.messages = session.messages.concat();
});
},
onError(error) {
const isAborted = error.message.includes("aborted");
const isAborted = error.message?.includes?.("aborted");
botMessage.content +=
"\n\n" +
prettyObject({
@@ -543,8 +572,13 @@ export const useChatStore = createPersistStore(
const config = useAppConfig.getState();
const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
// skip summarize when using dalle3?
if (isDalle3(modelConfig.model)) {
return;
}
const api: ClientApi = getClientApi(modelConfig.providerName);
const providerName = modelConfig.providerName;
const api: ClientApi = getClientApi(providerName);
// remove error messages if any
const messages = session.messages;
@@ -567,6 +601,7 @@ export const useChatStore = createPersistStore(
config: {
model: getSummarizeModel(session.mask.modelConfig.model),
stream: false,
providerName,
},
onFinish(message) {
get().updateCurrentSession(
@@ -660,7 +695,8 @@ export const useChatStore = createPersistStore(
set(() => ({ sessions }));
},
clearAllData() {
async clearAllData() {
await indexedDBStorage.clear();
localStorage.clear();
location.reload();
},

View File

@@ -1,4 +1,5 @@
import { LLMModel } from "../client/api";
import { DalleSize, DalleQuality, DalleStyle } from "../typing";
import { getClientConfig } from "../config/client";
import {
DEFAULT_INPUT_TEMPLATE,
@@ -33,6 +34,7 @@ export const DEFAULT_CONFIG = {
submitKey: SubmitKey.Enter,
avatar: "1f603",
fontSize: 14,
fontFamily: "",
theme: Theme.Auto as Theme,
tightBorder: !!config?.isApp,
sendPreviewBubble: true,
@@ -60,6 +62,9 @@ export const DEFAULT_CONFIG = {
compressMessageLengthThreshold: 1000,
enableInjectSystemPrompts: true,
template: config?.template ?? DEFAULT_INPUT_TEMPLATE,
size: "1024x1024" as DalleSize,
quality: "standard" as DalleQuality,
style: "vivid" as DalleStyle,
},
};

View File

@@ -2,3 +2,4 @@ export * from "./chat";
export * from "./update";
export * from "./access";
export * from "./config";
export * from "./plugin";

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