Compare commits

...

684 Commits

Author SHA1 Message Date
Yifei Zhang
2bfb362832 Update tauri.conf.json 2023-11-10 15:19:31 +08:00
Yidadaa
cb140e482f fix: #3207 ensure corner case 2023-11-10 15:15:43 +08:00
Yifei Zhang
e6b72ac1ff Update README.md 2023-11-10 02:59:47 +08:00
Yifei Zhang
8032e6d68d Update README.md 2023-11-10 02:59:30 +08:00
Yidadaa
c7e0a6f37f doc: update azure env vars 2023-11-10 02:57:46 +08:00
Yifei Zhang
1141cd2e6e Merge pull request #3206 from Yidadaa/azure 2023-11-10 02:55:12 +08:00
Yidadaa
c9dd953817 fixup 2023-11-10 02:50:50 +08:00
Yidadaa
b7ffca031e feat: close #935 add azure support 2023-11-10 02:43:37 +08:00
Yifei Zhang
fd2f441e02 feat: wont send max_tokens 2023-11-09 20:45:25 +08:00
Yifei Zhang
3b3ebda34b fix: #3196 3.5-turbo-1106 should use old cutoff date 2023-11-09 11:01:41 +08:00
Yifei Zhang
87e3d663a2 fix: #3192 use smaller max_tokens as default 2023-11-09 11:00:09 +08:00
Yidadaa
d0a1d910d4 fix: #3186 enable max_tokens in chat payload 2023-11-09 03:19:13 +08:00
Yifei Zhang
33b97082fa Merge pull request #3192 from Yidadaa/bugfix-1109 2023-11-09 03:05:41 +08:00
Yidadaa
d93f05f511 feat: close #3187 use CUSTOM_MODELS to control model list 2023-11-09 03:01:29 +08:00
Yifei Zhang
3dc29197e1 Merge pull request #3191 from Yidadaa/bugfix-1109 2023-11-09 02:23:20 +08:00
Yidadaa
fbc0236748 fix: #3174 should prompt to confirm to delete chat 2023-11-09 02:03:05 +08:00
Yifei Zhang
1304a3943a Merge pull request #3190 from Yidadaa/bugfix-1109 2023-11-09 01:54:09 +08:00
Yidadaa
8c0ba1aee2 feat: close #2954 chat summary should be copyable 2023-11-09 01:51:33 +08:00
Yifei Zhang
47154773f2 Merge pull request #3181 from AnYiEE/docs-typo 2023-11-08 18:37:59 +08:00
Yifei Zhang
deca9bf06d Merge pull request #3183 from hekbobo/patch-1 2023-11-08 18:37:35 +08:00
bob
6ab4c9be2e Update cloudflare-pages-cn.md
cloudflare 构建命令
2023-11-08 17:40:28 +08:00
安忆
3a519612a0 docs: fix typo in #3171 2023-11-08 15:53:45 +08:00
Yifei Zhang
d1ec26ae83 Merge pull request #3171 from AnYiEE/docs-update 2023-11-08 15:31:17 +08:00
安忆
9cb889c34f docs: up to date 2023-11-08 07:09:52 +08:00
Yifei Zhang
77f27b7948 Merge pull request #3166 from Yidadaa/bugfix-1107 2023-11-08 01:08:00 +08:00
Yifei Zhang
dbee5bc515 Update tauri.conf.json 2023-11-08 01:05:07 +08:00
Yidadaa
54a5332834 feat: sort model by name 2023-11-08 01:04:20 +08:00
Yifei Zhang
8af164c99f Merge pull request #3165 from Yidadaa/bugfix-1107 2023-11-08 00:36:09 +08:00
Yidadaa
c7321fddfb fixup 2023-11-08 00:34:31 +08:00
Yidadaa
c5ca278253 feat: close #2908 allow to disable parse settings from link 2023-11-08 00:30:02 +08:00
Yidadaa
638fdd8c3e feat: default disable balance query 2023-11-08 00:20:34 +08:00
Yifei Zhang
3407b57a51 Merge pull request #3163 from Yidadaa/bugfix-1107 2023-11-07 23:56:29 +08:00
Yidadaa
836bf836d3 fix: #3152 system prompt should be injected 2023-11-07 23:54:30 +08:00
Yidadaa
6e52d14180 fix: #3016 disable sidebar transition on ios 2023-11-07 23:30:09 +08:00
Yifei Zhang
1096fe55e1 Merge pull request #3143 from H0llyW00dzZ/newmodels 2023-11-07 10:58:40 +08:00
H0llyW00dzZ
d2d615c84a Refactor KnowledgeCutoff
[+] fix(constant.ts): update DEFAULT_SYSTEM_TEMPLATE to include knowledgeCutoff and time variables
[+] feat(chat.ts): add support for injecting system prompts based on model configuration
2023-11-07 06:42:55 +07:00
H0llyW00dzZ
9f26c8cecb Fix Typo [GPT-4]
[+] fix(constant.ts): fix typo in model name, change "ggpt-4-1106-preview" to "gpt-4-1106-preview"
2023-11-07 06:33:07 +07:00
H0llyW00dzZ
cd1f082c52 Feat Models [GPT-4] [GPT-3.5] [128K context]
[+] feat(constant.ts): add new models to DEFAULT_MODELS array
2023-11-07 06:08:06 +07:00
Yifei Zhang
b90dfb48ee Merge pull request #3095 from Yidadaa/revert-3013-main 2023-10-26 18:34:44 +08:00
Yifei Zhang
70e67a686b Revert "fix serviceWorker cache: auto caching files by workbox" 2023-10-26 00:06:09 +08:00
Yifei Zhang
d1c3d900ab Update README_KO.md 2023-10-23 14:38:26 +08:00
Yifei Zhang
e8a4ad1001 Update README_KO.md 2023-10-23 14:38:04 +08:00
Yifei Zhang
ccac85b079 Update README_CN.md 2023-10-23 14:37:48 +08:00
Yifei Zhang
f92fefb6cd Update synchronise-chat-logs-cn.md 2023-10-23 14:36:42 +08:00
Yifei Zhang
cc565cf57e Merge pull request #3013 from jessegpt/main 2023-10-16 12:02:26 +08:00
Yifei Zhang
4a56e9c884 Merge pull request #3019 from eltociear/patch-2 2023-10-16 12:02:04 +08:00
Yifei Zhang
d11d906fa2 Merge pull request #3033 from Yidadaa/Yidadaa-patch-1 2023-10-16 11:58:36 +08:00
Yifei Zhang
65c4a0c319 feat: close #3031 user can set larger font size 2023-10-16 11:52:45 +08:00
Yifei Zhang
55bcf78efe Update README.md 2023-10-15 20:08:08 +08:00
Ikko Eltociear Ashimine
f5a2ce52aa Fix typo in README.md
passsword -> password
2023-10-14 15:22:41 +09:00
Jesse
0dcbfd746e fix serviceWorker cache 2023-10-13 15:41:00 +04:00
Yifei Zhang
06e043d3f1 Merge pull request #3003 from suravshrestha/patch1 2023-10-13 00:41:16 +08:00
Surav Shrestha
986d34fb3e docs: fix typo in app/masks/en.ts 2023-10-12 21:22:35 +05:45
Yifei Zhang
5296ab1b09 Merge pull request #2996 from MCheping8108/main 2023-10-12 18:22:52 +08:00
mcheping520
b5d3348d99 已拆分单个文件、翻译多国语言版本 2023-10-12 18:17:38 +08:00
Yifei Zhang
1e77df381a Update constant.ts 2023-10-12 17:31:29 +08:00
mcheping520
ec33281ff5 提供同步聊天记录教程,同时翻译多国语言 2023-10-11 21:29:50 +08:00
Yifei Zhang
ba67f13ad5 Merge pull request #2993 from H0llyW00dzZ/masks 2023-10-11 11:42:45 +08:00
H0llyW00dzZ
1604ed91fb Merge branch 'Yidadaa:main' into masks 2023-10-11 07:35:40 +07:00
H0llyW00dzZ
74fcaab5e9 Refactor Mask UI Page [Masks]
[+] refactor(mask.tsx): refactor simple search to be case-insensitive
2023-10-11 04:36:04 +07:00
Yifei Zhang
adee435ea6 Merge pull request #2992 from ograycode/patch-1 2023-10-11 00:42:52 +08:00
Jason O'Gray
ea59ab5176 Fix type in sync.ts
Simple typo fix.
2023-10-10 11:24:45 -04:00
Yifei Zhang
f78008c1b2 Merge pull request #2983 from Yidadaa/Yidadaa-patch-1 2023-10-10 11:56:24 +08:00
Yifei Zhang
f54db695af fix: #2981 full screen button not works 2023-10-10 11:54:25 +08:00
Yifei Zhang
f21f922160 Update tauri.conf.json 2023-10-09 22:46:36 +08:00
Yifei Zhang
fd413c7b52 Merge pull request #2947 from KeithHello/main 2023-10-09 22:45:56 +08:00
Yifei Zhang
3e2c5af4b5 Merge pull request #2975 from luckykong/patch-1 2023-10-09 22:38:45 +08:00
Kong Gaowen
bdb49b1171 修改界面
似乎这里引入了一个逻辑判断错误,会导致打包之后的界面变宽变高。
2023-10-09 18:50:10 +08:00
GH Action - Upstream Sync
5933b3d7eb Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web 2023-10-08 00:19:29 +00:00
Yifei Zhang
4c8d606fae Merge pull request #2950 from PeterDaveHello/locale-tw 2023-10-08 00:20:39 +08:00
Yifei Zhang
13c1d2fd2b Merge pull request #2946 from H0llyW00dzZ/ClientApp 2023-10-07 23:50:49 +08:00
Yifei Zhang
e35c807216 Update tauri.conf.json 2023-10-07 23:48:50 +08:00
Yifei Zhang
5a2cc6f154 Merge pull request #2927 from lifeeric/MACOS 2023-10-07 23:10:43 +08:00
Peter Dave Hello
88f8c43472 Improve tw locale 2023-10-07 23:06:20 +08:00
Yifei Zhang
ef3e8e6fac Merge pull request #2933 from H0llyW00dzZ/authpage 2023-10-07 23:03:56 +08:00
KeithHello
1505372e20 Change log
-   config.ts
    -   line 72: remove type confirmation of x as x always has type 'number'
    -   line 135: remove the redundant local variable
-   chat.ts
    -   delete the unused import
2023-10-04 18:08:29 +09:00
KeithHello
ad5093ce05 Change log
-   config.ts
    -   line 72 remove type confirmation of x as x always has type 'number'
    -   line 135 remove redundant local variable
-   chat.ts
    -   delete unused import
2023-10-04 17:40:23 +09:00
H0llyW00dzZ
b558d1afc6 Feat & Fix "Client App [Notification]"
[+] feat(update.ts): add support for localization in update notifications
[+] fix(update.ts): add missing semicolon in useUpdateStore function
2023-10-04 02:10:26 +07:00
H0llyW00dzZ
ddfd05b008 Fix & Feat Client App [Notification]
[+] fix(update.ts): remove unnecessary notification sending when permission is not granted
[+] feat(update.ts): add notification for already up to date version
2023-10-03 09:12:41 +07:00
H0llyW00dzZ
d2ad01a9ff Client App Fix Issue [Bug] 'export' button does not work #2884
[+] fix(exporter.tsx): add async keyword to download function
[+] feat(exporter.tsx): add support for saving image file using window.__TAURI__ API
[+] feat(global.d.ts): add types for window.__TAURI__ API methods
[+] feat(locales): add translations for download success and failure messages
[+] feat(sync.ts): add support for generating backup file name with date and time
[+] fix(utils.ts): add async keyword to downloadAs function and add support for saving file using window.__TAURI__ API
2023-10-03 08:49:03 +07:00
H0llyW00dzZ
64a17abfe2 Client App [Notification]
[+] feat(global.d.ts): add support for window.__TAURI__.notification methods
[+] feat(update.ts): add notification for new version availability
[+] fix(Cargo.toml): add tauri feature "notification-all" to enable notifications
[+] fix(tauri.conf.json): enable all notification features in tauri configuration
2023-10-03 08:08:11 +07:00
H0llyW00dzZ
04b638aa06 Fix & Refactor UI Page [Auth Page]
[+] fix(auth.tsx): fix conditional rendering of token input field
[+] refactor(auth.tsx): improve code readability by using conditional rendering for token input field
2023-10-01 00:23:19 +07:00
H0llyW00dzZ
31e30906d0 Refactor Locale Indonesia
[+] refactor(id.ts): remove unused import and isApp variable
[+] fix(id.ts): update Unauthorized error message
2023-09-30 22:59:24 +07:00
H0llyW00dzZ
bc00be9065 Feat & Fix UI Page [Auth Page]
[+] feat(auth.tsx): add goChat function to navigate to chat page
[+] fix(auth.tsx): change onClick event from goHome to goChat
2023-09-30 22:33:18 +07:00
H0llyW00dzZ
4a599e986f UI Page [Auth Page]
[+] feat(auth.tsx): add support for resetting access token in resetAccessCode function
[+] fix(auth.tsx): fix formatting issue in resetAccessCode function
[+] feat(locales): add support for sub tips in Auth component for multiple languages :
  - Add sub tips in Arabic locale (ar.ts)
  - Add sub tips in Bengali locale (bn.ts)
  - Add sub tips in Chinese locale (cn.ts)
  - Add sub tips in English locale (en.ts)
  - Add sub tips in Indonesian locale (id.ts)
2023-09-30 22:16:34 +07:00
EricGit
f1ca03e378 [FIXED] now it should detects all macintosh 2023-09-28 13:21:17 -04:00
Eric R
f3d5fc7a84 [FIXED] now the default key should be CMD on MacOS 2023-09-28 06:50:31 -04:00
Eric R
3bfcdf9c41 [ADDED] MacOS detect 2023-09-28 06:10:22 -04:00
Yifei Zhang
144200e315 Merge pull request #2912 from Algorithm5838/Algorithm5838-patch-1 2023-09-27 17:18:15 +08:00
Algorithm5838
398e229c77 Update chat.tsx 2023-09-26 21:49:17 +03:00
Algorithm5838
6a61fe5776 Update chat.tsx 2023-09-26 20:45:09 +03:00
Yifei Zhang
9835206452 Merge pull request #2905 from H0llyW00dzZ/ref#2895 2023-09-26 10:50:49 +08:00
H0llyW00dzZ
70b0580fb7 UI Page [sidebar]
[+] fix(sidebar.tsx): update onClick function to conditionally navigate to different paths based on config.dontShowMaskSplashScreen value

Ref : [Feature] Make the mask selection more streamlined Yidadaa#2895
2023-09-26 04:59:19 +07:00
Yifei Zhang
23eb7732d7 feat: support more http status check for webdav 2023-09-20 17:47:35 +08:00
Yifei Zhang
26e50cefea Update chat.tsx 2023-09-20 02:09:14 +08:00
Yifei Zhang
588e907181 Merge pull request #2873 from Yidadaa/bugfix-0919 2023-09-19 23:42:09 +08:00
Yidadaa
eae7d6260f fix: should not tight border in desktop app 2023-09-19 23:26:52 +08:00
Yifei Zhang
175b4e7f92 Update README_CN.md 2023-09-19 11:04:10 +08:00
Yifei Zhang
b050417ab1 Update tauri.conf.json 2023-09-19 11:03:22 +08:00
Yifei Zhang
37b49400db Update constant.ts 2023-09-19 11:03:03 +08:00
Yifei Zhang
ebcb2e7837 Merge pull request #2867 from Yidadaa/bugfix-0919 2023-09-19 03:34:45 +08:00
Yidadaa
f1e7db6a88 feat: auto fill upstash backup name 2023-09-19 03:33:17 +08:00
Yifei Zhang
2ba0929458 Merge pull request #2866 from Yidadaa/bugfix-0919 2023-09-19 03:20:37 +08:00
Yidadaa
83fed42997 feat: add upstash redis cloud sync 2023-09-19 03:18:34 +08:00
Yifei Zhang
2c4626709c Merge pull request #2865 from Yidadaa/bugfix-0919 2023-09-19 02:25:35 +08:00
Yidadaa
59fbadd9eb fixup 2023-09-19 02:21:31 +08:00
Yidadaa
adb860b464 fix: #2820 try to fix 520 error code 2023-09-19 02:12:43 +08:00
Yifei Zhang
8d8790586d Merge pull request #2864 from Yidadaa/bugfix-0919 2023-09-19 02:02:18 +08:00
Yidadaa
61ca60c550 fix: #2817 min-height for landscape orientation on mobile phone 2023-09-19 01:58:52 +08:00
Yidadaa
d713d01600 feat: close #2848 click drag icon to toggle sidebar width 2023-09-19 01:47:15 +08:00
Yifei Zhang
372ea0f845 Merge pull request #2853 from a6z6/main 2023-09-18 17:33:09 +08:00
Yifei Zhang
61888708f5 Merge pull request #2856 from koho/fix-font 2023-09-18 17:32:56 +08:00
Gerhard Tan
c900459f73 Encode google font url 2023-09-18 10:22:07 +08:00
Amor Zara
2c92f75c86 Update .env.template 2023-09-17 17:32:42 +08:00
Yifei Zhang
3e1514239c Merge pull request #2839 from a6z6/main 2023-09-15 15:17:46 +08:00
Yifei Zhang
0707a1d49a Merge pull request #2840 from Algorithm5838/Algorithm5838-patch-1 2023-09-15 15:17:35 +08:00
Amor Zara
9521f19507 Merge pull request #1 from a6z6/a6z6-patch-1
Update route.ts
2023-09-15 11:23:42 +08:00
Amor Zara
bd69116df2 Update route.ts
Correct typo error and make warning more specific.
2023-09-15 11:21:42 +08:00
Algorithm5838
6535986484 Update markdown.tsx 2023-09-15 06:06:34 +03:00
Yifei Zhang
e03db9c2d5 Merge pull request #2826 from H0llyW00dzZ/language 2023-09-14 10:49:44 +08:00
Yifei Zhang
038790370c Merge pull request #2822 from yhua1998/main 2023-09-13 22:40:03 +08:00
H0llyW00dzZ
261bf0b298 Changes "Nama" -> "User" 2023-09-13 20:49:22 +07:00
H0llyW00dzZ
5a7bdcfe59 [+] Some improvements to the Indonesian language. 2023-09-13 20:17:30 +07:00
H0llyW00dzZ
4f3261b262 [+] Some improvements to the Indonesian language. 2023-09-13 20:05:58 +07:00
yhua1998
48e6087b1b fix: The width of the sidebar changes abruptly by dragging it multiple times over and over again (bouncing) 2023-09-13 16:55:31 +08:00
yhua1998
28103c901d Refactor: sidebar drag 2023-09-13 16:24:21 +08:00
yhua1998
368701610f fix: Width changes abruptly when dragging the sidebar (jumps)
In useRef is non-responsive, we can't get the modified config.sidebarWidth dynamic modification value in handleMouseUp
2023-09-13 13:57:30 +08:00
Yifei Zhang
b589f48aa9 Update tauri.conf.json 2023-09-13 03:01:28 +08:00
Yifei Zhang
74e32efa7d Merge pull request #2819 from Yidadaa/webdav 2023-09-13 02:56:10 +08:00
Yidadaa
dc555b2206 fixup 2023-09-13 02:52:28 +08:00
Yidadaa
859cf6930f fixup 2023-09-13 02:51:57 +08:00
Yidadaa
6f83fbd212 feat: add webdav support 2023-09-13 02:51:02 +08:00
Yifei Zhang
a7be4780ba Merge pull request #2809 from kfear1337/auth-page 2023-09-12 18:14:13 +08:00
B0zal
b5e89d4440 [+] Updated Auth Page
- Made changes to the Auth Page to reset the input field for the access code when the "Later" button is clicked.
This ensures that only expected user is logged by entering access code or entering their OpenAI API Key, mitigating the risk of small bug issue
2023-09-12 07:00:23 +07:00
Yifei Zhang
89bc98d26b Merge pull request #2804 from Yidadaa/dependabot/npm_and_yarn/emoji-picker-react-4.5.1 2023-09-11 20:37:16 +08:00
Yifei Zhang
31a73273ac Merge pull request #2805 from Yidadaa/dependabot/npm_and_yarn/react-router-dom-6.15.0 2023-09-11 20:37:01 +08:00
Yifei Zhang
2a820ea6e7 Merge pull request #2808 from Yidadaa/dependabot/npm_and_yarn/eslint-8.49.0 2023-09-11 20:36:47 +08:00
Yifei Zhang
6484d4f0bd Merge pull request #2807 from Yidadaa/dependabot/npm_and_yarn/typescript-5.2.2 2023-09-11 20:36:33 +08:00
Yifei Zhang
461e48be32 Merge pull request #2799 from kfear1337/CodeQL-Report 2023-09-11 20:35:04 +08:00
dependabot[bot]
ff60ffca3e chore(deps-dev): bump eslint from 8.44.0 to 8.49.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.44.0 to 8.49.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.44.0...v8.49.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:32:34 +00:00
dependabot[bot]
1bbf310c46 chore(deps-dev): bump typescript from 4.9.5 to 5.2.2
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.2.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.5...v5.2.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:32:25 +00:00
dependabot[bot]
8469f448b5 chore(deps): bump react-router-dom from 6.14.1 to 6.15.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.14.1 to 6.15.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.15.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:32:00 +00:00
dependabot[bot]
e36abc3ac6 chore(deps): bump emoji-picker-react from 4.4.8 to 4.5.1
Bumps [emoji-picker-react](https://github.com/ealush/emoji-picker-react) from 4.4.8 to 4.5.1.
- [Release notes](https://github.com/ealush/emoji-picker-react/releases)
- [Commits](https://github.com/ealush/emoji-picker-react/commits)

---
updated-dependencies:
- dependency-name: emoji-picker-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 10:31:50 +00:00
B0zal
605dd72354 [+] CodeQL Report Fix log injection vulnerability in useSyncStore
Severity : High
Sanitize the 'res' object before logging it in the 'check' method of useSyncStore to prevent log injection attacks.
The 'res' object is now sanitized by extracting only the necessary properties ('status', 'statusText', and 'headers') and logging the sanitized object instead.
This ensures that only safe and expected data is logged, mitigating the risk of log injection vulnerabilities.
2023-09-11 08:49:08 +07:00
Yifei Zhang
9770b65146 Merge pull request #2798 from Yidadaa/backup 2023-09-11 00:41:25 +08:00
Yidadaa
57158890c3 fixup 2023-09-11 00:39:56 +08:00
Yifei Zhang
6f8f490fdd Merge pull request #2797 from Yidadaa/backup 2023-09-11 00:36:24 +08:00
Yidadaa
415e9dc913 fixup: minor sync fixup 2023-09-11 00:34:51 +08:00
Yifei Zhang
1487762925 Merge pull request #2796 from Yidadaa/backup 2023-09-11 00:27:51 +08:00
Yidadaa
c73a91a0f5 fixup: fix type errors 2023-09-11 00:24:05 +08:00
Yidadaa
5dced28088 fixup: add en locales 2023-09-11 00:22:14 +08:00
Yidadaa
38f6956e71 feat: close #2754 add import/export to file 2023-09-11 00:20:23 +08:00
Yifei Zhang
93da9dd1f4 Merge pull request #2791 from shoito/patch-1 2023-09-10 21:47:17 +08:00
shoito
505c8cde81 improve japanese translations 2023-09-09 16:10:24 +09:00
Yifei Zhang
f7a6fa9873 Update README_CN.md 2023-09-07 17:43:17 +08:00
Yifei Zhang
70a2d11b8c Merge pull request #2781 from rickyrobinett/main 2023-09-07 10:40:26 +08:00
Ricky Robinett
2c077aca5a fix cloudflare deployment instructions 2023-09-06 15:36:12 -04:00
Yifei Zhang
efef6b098d Merge pull request #2764 from Yidadaa/bugfix-0905 2023-09-05 02:10:53 +08:00
Yidadaa
885f2a3226 feat: close #2752 auto re-fill unfinished input 2023-09-05 01:54:28 +08:00
Yifei Zhang
1f94ae7888 Merge pull request #2721 from Yidadaa/revert-2673-dependabot/npm_and_yarn/next-13.4.19 2023-08-28 11:10:34 +08:00
Yifei Zhang
05aa520669 Revert "chore(deps): bump next from 13.4.9 to 13.4.19" 2023-08-28 11:06:12 +08:00
Yifei Zhang
b0c32159e7 Update tauri.conf.json 2023-08-28 01:15:46 +08:00
Yifei Zhang
4a182517da Update README.md 2023-08-28 01:04:24 +08:00
Yifei Zhang
107b584085 Merge pull request #2705 from kfear1337/language 2023-08-28 01:02:23 +08:00
Yifei Zhang
6d8416f838 Merge pull request #2713 from kfear1337/CodeQL-Report 2023-08-28 00:54:58 +08:00
Yifei Zhang
876a0cb6f7 Merge pull request #2717 from Yidadaa/bugfix-0827 2023-08-28 00:51:54 +08:00
Yidadaa
4904612523 feat: close #2430 add a simple user maual 2023-08-28 00:50:53 +08:00
Yidadaa
3bd76b9156 feat: close #2580 only use 3.5 to summarize when not using custom models 2023-08-28 00:02:52 +08:00
Yifei Zhang
dd757e7d04 Merge pull request #2711 from huhuhuni/main 2023-08-27 19:33:03 +08:00
B0zal
71d06647d0 Delete codeql.yml
it won't work if someone try, must have licence advance security
2023-08-27 09:15:05 +07:00
B0zal
0ae57589a9 This branch only for scanner 2023-08-27 06:09:55 +07:00
B0zal
22a6819f7b Security Update
[+] Protect Prototype
2023-08-26 22:16:29 +07:00
B0zal
5e23ad2db1 Security Update
Potentially unsafe external link
2023-08-26 22:16:29 +07:00
Yifei Zhang
e009ea57b9 Merge pull request #2706 from kfear1337/bug 2023-08-26 19:58:46 +08:00
B0zal
63c93a42b5 [+] Fixed language missing for finetuned 2023-08-25 18:15:50 +07:00
B0zal
e6cc1625b5 [+] FineTuned Sysmessage Depends for local language
this for JSON Exporter
2023-08-25 17:48:25 +07:00
B0zal
f65b0128e7 Issue #2702 should be fixed now kiw kiw
where the ChatGPTicon.src
glitches and breaks when exporting the image for the second time without refreshing the page.
2023-08-25 17:46:19 +07:00
B0zal
507b7fee56 [+] Language indonesia (my country) 2023-08-25 17:42:02 +07:00
huni
925d28495a fix bug of ! near check of system. 2023-08-24 23:24:26 +08:00
huni
19dd71eb05 setup shell support for debian 2023-08-24 22:04:42 +08:00
Yifei Zhang
b3fd56c2c1 Merge pull request #2685 from reece00/456 2023-08-24 11:12:47 +08:00
Yifei Zhang
d8b6ebf6cb fix: #2699 remove double quotes in readme 2023-08-24 11:09:17 +08:00
Yifei Zhang
bdaac17886 Merge pull request #2698 from kfear1337/main 2023-08-24 10:58:47 +08:00
Yifei Zhang
8cac51abbe chore: #2699 Update README.md 2023-08-24 10:54:28 +08:00
B0zal
0113d4499b [Feature] Better JSON Exporter #2692
[+] A view looks better
[+] auto minify json when click a copy in markdown and download

Co-Authored-By: wangwentong-lunaon <39506652+wangwentong-lunaon@users.noreply.github.com>
Co-Authored-By: Yifei Zhang <yidadaa@qq.com>
Co-Authored-By: B0zal <48602426+kfear1337@users.noreply.github.com>
2023-08-23 21:14:43 +07:00
Yifei Zhang
7562ab3c1c Merge pull request #2690 from pengooseDev/main 2023-08-23 19:06:01 +08:00
pengoosedev
d3de07ecf3 docs: Add vercel-ko 2023-08-23 01:33:46 +09:00
pengoosedev
b146cd889f Add faq-ko 2023-08-23 01:28:54 +09:00
pengoosedev
bc0f184098 docs: Add cloudflare-pages-ko 2023-08-23 01:08:31 +09:00
pengoosedev
1debde3046 docs: Add README_KO 2023-08-23 00:08:08 +09:00
reece00
4d3fdbdd80 The mobile terminal ishitbottom does not perform -10 2023-08-22 12:35:51 +08:00
Yifei Zhang
90c201d438 Merge pull request #2673 from Yidadaa/dependabot/npm_and_yarn/next-13.4.19 2023-08-22 11:43:59 +08:00
Yifei Zhang
1e22abab80 Merge pull request #2674 from Yidadaa/dependabot/npm_and_yarn/eslint-config-next-13.4.19 2023-08-22 11:43:38 +08:00
Yifei Zhang
792c37735e Merge pull request #2675 from Yidadaa/dependabot/npm_and_yarn/mermaid-10.3.1 2023-08-22 11:43:26 +08:00
Yifei Zhang
6d8ece530f Merge pull request #2677 from Yidadaa/dependabot/npm_and_yarn/prettier-3.0.2 2023-08-22 11:43:06 +08:00
dependabot[bot]
3499dfb285 chore(deps-dev): bump prettier from 2.8.8 to 3.0.2
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.8 to 3.0.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.8...3.0.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 10:57:49 +00:00
dependabot[bot]
30c656ceda chore(deps): bump mermaid from 10.2.3 to 10.3.1
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 10.2.3 to 10.3.1.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/v10.2.3...v10.3.1)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 10:57:23 +00:00
dependabot[bot]
a1c7f86ff3 chore(deps-dev): bump eslint-config-next from 13.2.3 to 13.4.19
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 13.2.3 to 13.4.19.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v13.4.19/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 10:57:07 +00:00
dependabot[bot]
4952e41132 chore(deps): bump next from 13.4.9 to 13.4.19
Bumps [next](https://github.com/vercel/next.js) from 13.4.9 to 13.4.19.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.4.9...v13.4.19)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 10:56:27 +00:00
Yifei Zhang
e1142216ec fix: #2672 should use correct resend index 2023-08-21 18:33:45 +08:00
Yifei Zhang
a4040fc1ee Update README_CN.md 2023-08-21 18:30:44 +08:00
Yifei Zhang
f84572443f Update faq-cn.md 2023-08-21 16:00:19 +08:00
Yifei Zhang
fbf6bc1979 Merge pull request #2655 from ClarenceDan/main 2023-08-19 18:01:09 +08:00
GH Action - Upstream Sync
3aeb2c1230 Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web 2023-08-19 01:06:19 +00:00
Yifei Zhang
50eb7a5f98 Update tauri.conf.json 2023-08-18 18:47:54 +08:00
Yifei Zhang
484d1e7396 Merge pull request #2649 from pichuang/zh-tw 2023-08-18 18:40:30 +08:00
Yifei Zhang
a632e13334 Merge pull request #2653 from Algorithm5838/Algorithm5838-patch-1 2023-08-18 18:40:18 +08:00
Clarence Dan
aa3f96f89c Update globals.scss 2023-08-18 18:18:16 +08:00
Clarence Dan
16685ddb6c Update chat.module.scss 2023-08-18 17:21:58 +08:00
Clarence Dan
e78b15b9f0 Update chat.module.scss
Specify styles for iOS devices.
2023-08-18 17:12:02 +08:00
Algorithm5838
35b0bd76f8 Update markdown.tsx 2023-08-18 08:12:27 +03:00
Phil Huang
1d7286c161 Improve the text in tw.ts
Adoption of text that is more closely aligned with the usage of zh-tw
2023-08-17 23:50:38 +08:00
Yifei Zhang
5a7ec38ecd Update README.md 2023-08-16 11:22:51 +08:00
Yifei Zhang
ada4e3cdcd Merge pull request #2641 from Yidadaa/bugfix-0815 2023-08-15 23:09:15 +08:00
Yidadaa
ed62c87156 feat: close #2638 hide auth page and use better unauth tips 2023-08-15 22:50:42 +08:00
Yidadaa
840277f584 fix: #2566 click avatar to edit context messages 2023-08-15 22:42:55 +08:00
Yifei Zhang
ffddb7fb2b Merge pull request #2636 from wangwentong-lunaon/main 2023-08-15 22:09:14 +08:00
wangwentong
b380421fd5 support json export 2023-08-15 13:32:34 +08:00
Yifei Zhang
20882a7598 Merge pull request #2629 from Yidadaa/bugfix-0814 2023-08-14 22:13:56 +08:00
Yifei Zhang
9d3dff47d2 Merge pull request #2627 from imldy/dev_title-generation-checkbox 2023-08-14 22:12:26 +08:00
Yidadaa
db5c7aba78 fix: #2615 scrollbar jitter under certain message counts 2023-08-14 22:11:38 +08:00
Yidadaa
e8e01aa60d feat: close #2618 use correct html lang attr 2023-08-14 21:55:18 +08:00
Yidadaa
ae8226907f feat: close #2621 use better default api url 2023-08-14 21:36:29 +08:00
imldy
803b66ae9d chore: Concise description 2023-08-14 20:47:02 +08:00
imldy
20a508e2d6 feat: add autoGenerateTitle option
(cherry picked from commit 656ab94a9c4edfee820616b8cfc39f5ee9952a3a)
2023-08-14 20:42:09 +08:00
Yifei Zhang
25ada1eae9 Merge pull request #2617 from Yidadaa/rtl-fix 2023-08-14 11:13:56 +08:00
Yifei Zhang
808e4b38a3 fixup 2023-08-14 11:10:02 +08:00
Yifei Zhang
a496bc5a63 fix: #2614 better rtl detecting algo 2023-08-14 10:57:24 +08:00
Yifei Zhang
c94713475f Update globals.scss 2023-08-13 02:47:07 +08:00
Yifei Zhang
d45c61109f Merge pull request #2607 from eltociear/add_ja-docs 2023-08-13 00:07:37 +08:00
Ikko Eltociear Ashimine
99220d72da Add faq-ja.md 2023-08-11 20:28:18 +09:00
Ikko Eltociear Ashimine
887eaef1aa Add vercel-ja.md 2023-08-11 19:58:54 +09:00
Ikko Eltociear Ashimine
836a00e104 Add cloudflare-pages-ja.md 2023-08-11 19:49:36 +09:00
Ikko Eltociear Ashimine
8ee5061046 Add Japanese README 2023-08-11 19:34:21 +09:00
Yifei Zhang
61852b4021 Merge pull request #2598 from Yidadaa/Yidadaa-patch-1 2023-08-10 10:49:16 +08:00
Yifei Zhang
0b7de6f7b2 fix: #2594 trim the / 2023-08-10 10:47:06 +08:00
Yifei Zhang
9834a67cbd Update tauri.conf.json 2023-08-09 15:37:13 +08:00
Yifei Zhang
d85a4a0c9f Merge pull request #2590 from ZhangYichi-ZYc/ZhangYichi-ZYc-patch-1 2023-08-09 15:29:46 +08:00
Yifei Zhang
67c8ec6d7e chore: change ACCESS_CODE_PREFIX to nk- 2023-08-09 15:27:08 +08:00
Yifei Zhang
2a2dd7ea19 Merge pull request #2588 from eltociear/eltociear-patch-1
Fix typo in README.md
2023-08-09 09:17:55 +08:00
Ikko Eltociear Ashimine
153e7ac7e4 Fix typo in README.md
notifictions -> notifications
2023-08-09 01:22:48 +09:00
Yifei Zhang
9420fd4946 Merge pull request #2585 from Yidadaa/bugfix-0808 2023-08-08 21:38:42 +08:00
Yidadaa
b14c5cd89c fix: #2485 one-time-use body 2023-08-08 21:36:37 +08:00
Yidadaa
4ab9141429 fix: #2564 should not clear message when error 2023-08-08 21:24:45 +08:00
Yidadaa
769c2f9f49 feat: close #2583 do not summarize with gpt-4 2023-08-08 21:22:41 +08:00
Yifei Zhang
c41c498a9c Merge pull request #2573 from 7lsu/main 2023-08-08 16:00:45 +08:00
7lsu
d1096582a5 font family display enhance 2023-08-07 17:44:32 +08:00
Yifei Zhang
543989151f Update constant.ts 2023-08-04 19:24:10 +08:00
Yifei Zhang
7da83987e4 chore: use bigger page size and render msg count 2023-08-04 15:45:16 +08:00
Yidadaa
523d553dac fix: clear btn should display in correct place 2023-08-04 02:58:02 +08:00
Yifei Zhang
ff6f0e9546 Merge pull request #2557 from Yidadaa/bugfix-0804
feat: disable auto focus on mobile screen
2023-08-04 02:45:19 +08:00
Yidadaa
3e63f6ba34 feat: disable auto focus on mobile screen 2023-08-04 02:43:55 +08:00
Yifei Zhang
811b92d24e Merge pull request #2556 from Yidadaa/bugfix-0804
fixup: improve auto scroll algo
2023-08-04 02:40:34 +08:00
Yidadaa
bc5ddc4541 fixup: improve auto scroll algo 2023-08-04 02:39:32 +08:00
Yifei Zhang
44e43729bf Merge pull request #2555 from Yidadaa/bugfix-0804
feat: close #2545 improve lazy load message list
2023-08-04 02:18:55 +08:00
Yidadaa
203067c936 feat: close #2545 improve lazy load message list 2023-08-04 02:16:44 +08:00
Zhang Yichi
f3b508c088 Update Model Pricing.md
OpenAI has updated their model prices, reducing the input of GPT-3.5 to $0.0015/1000 tokens
2023-08-03 23:52:18 +08:00
Yifei Zhang
081d84f848 Update tauri.conf.json 2023-08-03 10:41:45 +08:00
Yifei Zhang
e381add944 Merge pull request #2542 from Yidadaa/yifei-bugfix-0802
chore: smaller auto scroll interval
2023-08-02 23:54:19 +08:00
Yidadaa
75d4eca722 chore: smaller auto scroll interval 2023-08-02 23:51:01 +08:00
Yifei Zhang
56904ad6b2 Merge pull request #2541 from Yidadaa/yifei-bugfix-0802
feat: improve chat action, auto scroll and edit model title
2023-08-02 23:40:50 +08:00
Yidadaa
b5ef552c25 feat: improve auto scroll ux and edit model title 2023-08-02 23:35:51 +08:00
Yidadaa
cbabb9392c feat: improve ChatAction ux 2023-08-02 22:53:36 +08:00
Yifei Zhang
531b3dcf9e Merge pull request #2531 from LeviBorodenko/main
Add link to authentication page to i18n
2023-08-02 15:35:05 +08:00
Levi Borodenko
d975daf3f0 locale: add link to authentication page in i18n 2023-08-01 16:17:26 +02:00
Yifei Zhang
6137d551fe Merge pull request #2525 from 7lsu/main
typo fix
2023-08-01 15:17:26 +08:00
7lsu
cf625e3542 typo fix 2023-08-01 10:16:36 +08:00
Yifei Zhang
e354fca4a4 Merge pull request #2524 from Yidadaa/bugfix-0731
fix: #2514 should not clear the message after editing message
2023-07-31 22:23:11 +08:00
Yidadaa
129e7afc16 fix: #2514 should not clear the message after editing message 2023-07-31 22:20:39 +08:00
Yifei Zhang
e83e0f6a33 Merge pull request #2486 from fernandoxu/main
fix(typo): ngnix -> nginx
2023-07-26 17:13:46 +08:00
fernandoxu
cf4f928b25 fix(typo): ngnix -> nginx 2023-07-26 10:06:06 +08:00
Yifei Zhang
e4d955e3f9 Merge pull request #2483 from Yidadaa/bugfix-0725
fix: #2393 try to fix chat list lag
2023-07-25 23:01:46 +08:00
Yidadaa
13576087f4 fix: #2393 try to fix chat list lag 2023-07-25 22:59:21 +08:00
Yifei Zhang
e74f6f3183 Merge pull request #2469 from Yidadaa/dependabot/npm_and_yarn/types/react-dom-18.2.7
chore(deps-dev): bump @types/react-dom from 18.0.11 to 18.2.7
2023-07-24 22:06:40 +08:00
dependabot[bot]
8302d1d3c1 chore(deps-dev): bump @types/react-dom from 18.0.11 to 18.2.7
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.0.11 to 18.2.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 10:33:12 +00:00
Yifei Zhang
b7320e6834 Update tauri.conf.json 2023-07-21 00:37:38 +08:00
Yifei Zhang
3193fe0235 Merge pull request #2452 from Yidadaa/bugfix-0720
feat: close #2449 edit / insert / delete messages modal
2023-07-21 00:25:33 +08:00
Yidadaa
7c2fa9f8a4 feat: close #2449 edit / insert / delete messages modal 2023-07-21 00:24:26 +08:00
Yidadaa
e5f6133127 feat: close #2447 pre-fill key/code/url 2023-07-20 23:17:58 +08:00
Yidadaa
0198c5b781 feat: close #2445 switch to mit license 2023-07-20 22:51:49 +08:00
Yifei Zhang
f535073ae4 Merge pull request #2431 from Hopelwj/main
设置页面-参数过滤空格
2023-07-19 20:14:13 +08:00
Hopelwj
b9895ecadd Merge branch 'Yidadaa:main' into main 2023-07-18 19:39:59 +08:00
liuweijie
322eb66fdf fix: useAccessStore filter spaces 2023-07-18 19:38:16 +08:00
Yifei Zhang
f0abdc80eb Update tauri.conf.json 2023-07-18 10:50:44 +08:00
Yifei Zhang
70a4f94c81 Update home.tsx 2023-07-17 11:33:19 +08:00
Yifei Zhang
f7e4b36746 Merge pull request #2412 from Yidadaa/fix-2409
fix: #2409 empty prompt id cause crash
2023-07-17 11:03:33 +08:00
Yifei Zhang
4e6b71ace5 Merge pull request #2410 from Lantianyou/feature/typing-metadata
Feature/typing-metadata
2023-07-17 11:01:39 +08:00
Yifei Zhang
c916cd1a87 Update mask.tsx 2023-07-17 11:00:45 +08:00
兰天游
442a529a72 feat: add type for import 2023-07-16 21:35:13 +08:00
兰天游
e1243f3d59 feat: add typings for metadata 2023-07-16 21:34:01 +08:00
Yifei Zhang
62f8cd1db6 Merge pull request #2406 from Yidadaa/bugfix-0716
feat: close #2376 add babel polyfill
2023-07-16 16:34:45 +08:00
Yidadaa
af5f67d459 feat: close #2376 add babel polyfill 2023-07-16 16:32:22 +08:00
Yifei Zhang
13424a893e Merge pull request #2405 from Yidadaa/bugfix-0716
fix: #2367 do not copy in async callback after sharing to ShareGPT
2023-07-16 16:17:40 +08:00
Yidadaa
30473ec41e fix: #2367 do not copy in async callback after sharing to ShareGPT 2023-07-16 16:14:57 +08:00
Yifei Zhang
24d382c70d Merge pull request #2404 from Yidadaa/bugfix-0716
feat: improve dnd icon
2023-07-16 16:14:25 +08:00
Yidadaa
3ddedc903e feat: improve dnd icon 2023-07-16 15:49:15 +08:00
Yifei Zhang
e3c279b8be Merge pull request #2389 from MarkShawn2020/fix-default-openai-url
fixed openai base url if empty
2023-07-16 15:20:26 +08:00
Yifei Zhang
d909b676c5 Update README.md 2023-07-16 15:08:53 +08:00
Yifei Zhang
08a8ac9971 Merge pull request #2388 from yikayiyo/feat-drag-prompts
feat: #2297 drag and drop in contextual prompts
2023-07-16 14:30:49 +08:00
Yifei Zhang
7073fd2f3b Merge pull request #2392 from imldy/dev_SetInjectSystemPrompts
fix: enable `enableInjectSystemPrompts` attribute for old sessions
2023-07-16 14:29:50 +08:00
imldy
a9f67a48a1 dev: use current inject configuration 2023-07-15 02:48:47 +08:00
imldy
fd058cc693 fix: enable enableInjectSystemPrompts attribute for old sessions 2023-07-15 01:32:39 +08:00
Yifei Zhang
c98df33003 Merge pull request #2390 from MarkShawn2020/fix-react-key
fixed react key
2023-07-15 01:28:56 +08:00
markshawn2020
3f8b14f5f8 fixed react key 2023-07-14 21:15:34 +08:00
markshawn2020
1c335a68e0 fixed openai base url if empty 2023-07-14 21:03:32 +08:00
legao
fb98050d9f feat: drag and drop in contextual prompts 2023-07-14 18:08:03 +08:00
Yifei Zhang
91cb7aa13d Merge pull request #2372 from Yidadaa/dependabot/npm_and_yarn/semver-6.3.1
chore(deps): bump semver from 6.3.0 to 6.3.1
2023-07-13 16:46:00 +08:00
dependabot[bot]
ab0f7cc0c9 chore(deps): bump semver from 6.3.0 to 6.3.1
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 01:58:09 +00:00
Yifei Zhang
b51f7f9a25 Merge pull request #2370 from Yidadaa/bugfix-0712
fix: sharegpt roles
2023-07-12 23:21:22 +08:00
Yidadaa
d275e32e70 fix: sharegpt roles 2023-07-12 23:19:46 +08:00
Yifei Zhang
22cf78d506 Merge pull request #2355 from fyl080801/main
fix: cloudflare headers
2023-07-11 23:57:03 +08:00
Yifei Zhang
c7c318b31e Merge pull request #2360 from imldy/fix_can't_edit_old_mask
fix: 迁移面具对象的key以解决无法编辑旧面具的问题
2023-07-11 23:48:02 +08:00
imldy
a4d012828c fix: migrated mask object key 2023-07-11 21:02:09 +08:00
yuanliang feng
72cfebd2bc Merge pull request #3 from fyl080801/deploy
Deploy
2023-07-11 17:08:50 +08:00
yuanliang feng
a832cfb343 Update common.ts 2023-07-11 15:46:40 +08:00
yuanliang feng
9ef680db57 Merge pull request #2 from fyl080801/main
update
2023-07-10 23:35:19 +08:00
yuanliang feng
2c09f4ef90 Merge pull request #1 from fyl080801/main
update
2023-07-10 23:23:20 +08:00
Yifei Zhang
c44454bf74 Merge pull request #2346 from Yidadaa/bugfix-0710
fix: #2336 resending message should delete origional messages
2023-07-10 23:22:53 +08:00
Yidadaa
ad1b9b7f6d fixup 2023-07-10 23:21:22 +08:00
Yidadaa
15e063e1b5 feat: #2330 disable /list/models 2023-07-10 23:19:43 +08:00
Yidadaa
c00a63e4c3 fix: #2336 resending message should delete origional messages 2023-07-10 22:59:12 +08:00
Yifei Zhang
dbda27b1d6 Merge pull request #2343 from Yidadaa/dependabot/npm_and_yarn/next-13.4.9
chore(deps): bump next from 13.4.6 to 13.4.9
2023-07-10 21:52:19 +08:00
Yifei Zhang
9eef879dbe Merge pull request #2342 from Yidadaa/dependabot/npm_and_yarn/tauri-apps/cli-1.4.0
chore(deps-dev): bump @tauri-apps/cli from 1.3.1 to 1.4.0
2023-07-10 21:52:08 +08:00
Yifei Zhang
cb29423636 Merge pull request #2341 from Yidadaa/dependabot/npm_and_yarn/types/react-18.2.14
chore(deps-dev): bump @types/react from 18.2.12 to 18.2.14
2023-07-10 21:51:56 +08:00
Yifei Zhang
d754eecf07 Merge pull request #2340 from Yidadaa/dependabot/npm_and_yarn/eslint-8.44.0
chore(deps-dev): bump eslint from 8.37.0 to 8.44.0
2023-07-10 21:51:45 +08:00
Yifei Zhang
1e308782ee Merge pull request #2339 from Yidadaa/dependabot/npm_and_yarn/react-router-dom-6.14.1
chore(deps): bump react-router-dom from 6.10.0 to 6.14.1
2023-07-10 21:51:32 +08:00
Yifei Zhang
69139cd85b Merge pull request #2344 from imldy/dev_SetInjectSystemPrompts
Dev: 给是否注入全局系统prompt提供一个开关
2023-07-10 21:51:06 +08:00
imldy
f59235bd5a feat: 翻译InjectSystemPrompts配置项为其他语言 2023-07-10 20:14:10 +08:00
imldy
2930ba0457 fix: 修改InjectSystemPrompts.SubTitle使其更符合系统行为 2023-07-10 20:12:51 +08:00
imldy
1513881eed fix: 修改是否应该注入System Prompt的判断规则为根据设置项 2023-07-10 18:57:54 +08:00
imldy
5e361f6748 dev: 增加模型设置项:是否启用注入全局 System Prompt 功能 2023-07-10 18:56:22 +08:00
dependabot[bot]
b182543a21 chore(deps): bump next from 13.4.6 to 13.4.9
Bumps [next](https://github.com/vercel/next.js) from 13.4.6 to 13.4.9.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.4.6...v13.4.9)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 10:51:20 +00:00
dependabot[bot]
cbce8b444f chore(deps-dev): bump @tauri-apps/cli from 1.3.1 to 1.4.0
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/cli.js-v1.3.1...@tauri-apps/cli-v1.4.0)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 10:48:41 +00:00
dependabot[bot]
38e92cfc62 chore(deps-dev): bump @types/react from 18.2.12 to 18.2.14
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.12 to 18.2.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 10:47:57 +00:00
dependabot[bot]
a7764dc6d5 chore(deps-dev): bump eslint from 8.37.0 to 8.44.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.37.0 to 8.44.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.37.0...v8.44.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 10:47:33 +00:00
dependabot[bot]
c74552c4c5 chore(deps): bump react-router-dom from 6.10.0 to 6.14.1
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.10.0 to 6.14.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.14.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 10:46:56 +00:00
yuanliang feng
7a0b437626 Update common.ts 2023-07-10 17:26:28 +08:00
Yifei Zhang
c7e9f13d2e Update tauri.conf.json 2023-07-10 15:03:02 +08:00
Yifei Zhang
51e816ad6d Merge pull request #2320 from yanCode/main
fix: typos
2023-07-10 14:25:47 +08:00
ShengYan, Zhang
dd047fd58f fix: typos 2023-07-10 10:09:19 +08:00
Yifei Zhang
300f023d2a Merge pull request #2318 from Yidadaa/bugfix-0709-2
chore: suppress module not found warning
2023-07-09 22:23:43 +08:00
Yidadaa
a282a80a6e chore: suppress module not found warning 2023-07-09 22:18:48 +08:00
Yifei Zhang
e668e17655 Merge pull request #2317 from Yidadaa/bugfix-0709-2
fix: #2303 should be able to select custom models
2023-07-09 22:12:04 +08:00
Yidadaa
a913d9728c fixup 2023-07-09 22:06:41 +08:00
Yifei Zhang
15862d393b Merge pull request #2316 from SergeWilfried/main
Add Missing FR Translations
2023-07-09 22:01:23 +08:00
Yidadaa
48a6cdd50a feat: improve model selector ui 2023-07-09 22:01:04 +08:00
Yidadaa
09b05cde7f fix: #2303 should select custom models 2023-07-09 21:56:49 +08:00
SergeWilfried
da294038a8 Merge pull request #2 from Yidadaa/main
Upstream Sync
2023-07-09 13:21:05 +00:00
Yifei Zhang
332f3fb8e8 Merge pull request #2314 from Yidadaa/bugfix-0709-2
feat: #920  migrate id to nanoid
2023-07-09 19:42:06 +08:00
Yidadaa
8e4743e719 feat: #920 migrate id to nanoid 2023-07-09 19:37:42 +08:00
Yifei Zhang
4a663e539a Merge pull request #2312 from Yidadaa/bugfix-0709-2
feat: close #2303 add custom model name config
2023-07-09 18:17:38 +08:00
Yidadaa
98ac7ee277 feat: close #2303 add custom model name config 2023-07-09 18:15:52 +08:00
Yidadaa
28c457730a fix: #2280 auto-detect models from 'list/models' 2023-07-09 18:03:06 +08:00
Yifei Zhang
9b61fe9df2 Merge pull request #2309 from Yidadaa/bugfix-0709
feat: #2308 #2294 #2295
2023-07-09 16:50:00 +08:00
Yidadaa
b55b01cb13 feat: #2308 improve chat actions ux 2023-07-09 16:39:46 +08:00
Yidadaa
90d8f3117f fix: #2295 use correct methods to migrate state 2023-07-09 16:28:15 +08:00
Yidadaa
c7e976c8c5 chore: update readme 2023-07-09 16:26:18 +08:00
Yidadaa
6014b765f4 feat: close #2294 add documents for adding a new translation 2023-07-09 16:26:00 +08:00
Yidadaa
ca295588c4 fix: #2308 improve chat actions 2023-07-09 16:15:58 +08:00
serge
437334355f chore(fr.ts): update French translations for improved user experience and clarity
feat(fr.ts): add new translations for pinning messages, editing, and input actions
feat(fr.ts): add new translations for commands and input actions related to conversation management
feat(fr.ts): add new translations for settings options related to resetting and clearing data
feat(fr.ts): add new translations for settings options related to language, font size, and input template
feat(fr.ts): add new translations for settings options related to update version and presence penalty
feat(fr.ts): add new translations for settings options related to masking and built-in assistants
feat(fr.ts): add new translations for settings options related to temperature and top P
feat(fr.ts): add new translations for settings options related to max tokens and presence penalty
feat(fr.ts): add new translations for settings options related to cloning and configuration
feat(fr.ts): add new translations for settings options related to avatar, name, sync, hide context, and sharing
2023-07-08 14:12:46 +00:00
Yifei Zhang
3432d4df29 Update README.md 2023-07-07 17:57:53 +08:00
Yifei Zhang
05fcb96181 Merge pull request #2275 from Saber-Kurama/pref/api-models
perf:  models接口返回异常数据的容错处理
2023-07-07 11:23:30 +08:00
Yifei Zhang
6bb27f90a4 Merge pull request #2277 from PaRaD1SE98/main
fix: selector z-index bug
2023-07-07 11:16:22 +08:00
PaRaD1SE98
c10e8382a9 fix: selector z-index bug 2023-07-07 04:15:05 +09:00
guochao
6653a31eb7 perf: models接口返回数据的容错处理 2023-07-06 20:00:30 +08:00
Yifei Zhang
42561d04de Update tauri.conf.json 2023-07-06 11:21:55 +08:00
Yidadaa
9a285ab935 fixup 2023-07-06 02:12:37 +08:00
Yifei Zhang
81771568be Merge pull request #2269 from Yidadaa/bugfix-0705
feat: improve image preview & model switcher model ux
2023-07-06 02:09:05 +08:00
Yifei Zhang
795b43f174 Merge pull request #2268 from imabutahersiddik/main
Added support for Bangla language.
2023-07-06 02:08:03 +08:00
Yidadaa
52203b50eb fixup 2023-07-06 02:07:35 +08:00
Yidadaa
0373b2c9dd feat: close #2266 use modal to switch model 2023-07-06 02:03:31 +08:00
ki-ask
fe2c1c4ec6 Added support for Bangla language 2023-07-05 23:55:43 +06:00
ki-ask
596b6542e8 Added support for Bangla language 2023-07-05 23:55:14 +06:00
ki-ask
6897bf1254 Added support for Bangla language 2023-07-05 23:39:22 +06:00
ki-ask
8a6a13e583 Added support for Bangla language 2023-07-05 23:35:55 +06:00
Yidadaa
b718285125 feat: improve svg viewer 2023-07-06 01:33:30 +08:00
ki-ask
e5ab918ef9 Added support for Bangla language 2023-07-05 23:31:45 +06:00
Yidadaa
6c6a2d08db feat: close #2267 display a modal to export image 2023-07-06 01:26:06 +08:00
Yidadaa
9e6617e3ca feat: add max icon for modals 2023-07-06 01:11:50 +08:00
Yifei Zhang
94a50f92e1 Merge pull request #2264 from Yidadaa/bugfix-0705
feat: close #1072 share mask as link
2023-07-05 23:21:49 +08:00
Yidadaa
5c8be2a8f6 feat: close #1072 share mask as link 2023-07-05 23:19:54 +08:00
Yifei Zhang
1dcf2d80b4 Merge pull request #2263 from Yidadaa/bugfix-0705
fix: #2252 polyfill composing for old safari browsers & fix #2261
2023-07-05 22:51:49 +08:00
Yidadaa
1197521921 fix: #2252 polyfill composing for old safari browsers 2023-07-05 22:50:12 +08:00
Yidadaa
3863cfe786 fix: #2261 default enable gpt-4 models 2023-07-05 22:41:10 +08:00
Yidadaa
54bd07702c refactor: #2256 hide builtin masks 2023-07-05 22:39:25 +08:00
Yifei Zhang
a75e2b0c0e Merge pull request #2256 from marcusschiesser/main
feat: add setting to hide built-in masks
2023-07-05 22:30:28 +08:00
Yifei Zhang
29fd9b23fe Merge pull request #2260 from sunls233/fix-summarize
修复摘要总结后不会被使用
2023-07-05 22:28:03 +08:00
sunls233
089e3b8946 fix summarize don't send 2023-07-05 14:27:37 +08:00
GH Action - Upstream Sync
9c36fcec81 Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web 2023-07-05 00:12:23 +00:00
Marcus Schiesser
74fa065266 feat: add setting to hide builtin masks 2023-07-05 00:07:26 +07:00
Yifei Zhang
38f2495cf6 Merge pull request #2253 from Yidadaa/bugfix-0704
feat: close #2192 use /list/models to get model ids
2023-07-04 23:21:32 +08:00
Yidadaa
4131fccbe0 feat: close #2192 use /list/models to get model ids 2023-07-04 23:16:24 +08:00
Yidadaa
f2d748cfe4 refactor: improve modal ux 2023-07-04 22:08:41 +08:00
Yifei Zhang
197ec0c29c Merge pull request #2242 from PaRaD1SE98/main
fix: temperature -> top_p
2023-07-04 11:28:43 +08:00
PaRaD1SE98
0a2af9335c fix: temperature -> top_p 2023-07-04 02:49:05 +09:00
Yifei Zhang
a52fa28ed1 Update tauri.conf.json 2023-07-04 01:10:16 +08:00
Yifei Zhang
78ed24dbf6 Merge pull request #2241 from Yidadaa/bugfix-0704
feat: add top p config
2023-07-04 00:42:41 +08:00
Yidadaa
cda074fe24 fixup 2023-07-04 00:40:57 +08:00
Yidadaa
823032617d feat: add top p config 2023-07-04 00:39:54 +08:00
Yidadaa
5963459499 fix: #2221 user prompts in front of all prompts 2023-07-04 00:22:30 +08:00
Yidadaa
0bc2c71b0c fix: #2230 hide chat actions for context prompts 2023-07-04 00:14:57 +08:00
Yifei Zhang
b4e350e189 Merge pull request #2234 from Yidadaa/dependabot/npm_and_yarn/prettier-2.8.8
chore(deps-dev): bump prettier from 2.8.7 to 2.8.8
2023-07-03 23:59:23 +08:00
Yifei Zhang
941e46490f Merge pull request #2235 from Yidadaa/dependabot/npm_and_yarn/rehype-katex-6.0.3
chore(deps): bump rehype-katex from 6.0.2 to 6.0.3
2023-07-03 23:59:12 +08:00
Yifei Zhang
4df92e903a Merge pull request #2236 from Yidadaa/dependabot/npm_and_yarn/hello-pangea/dnd-16.3.0
chore(deps): bump @hello-pangea/dnd from 16.2.0 to 16.3.0
2023-07-03 23:59:02 +08:00
Yifei Zhang
6ba02d0d50 Merge pull request #2237 from Yidadaa/dependabot/npm_and_yarn/types/node-20.3.3
chore(deps-dev): bump @types/node from 20.3.1 to 20.3.3
2023-07-03 23:58:47 +08:00
dependabot[bot]
2dc122831b chore(deps-dev): bump @types/node from 20.3.1 to 20.3.3
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.3.1 to 20.3.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 10:27:32 +00:00
dependabot[bot]
f3f84e523a chore(deps): bump @hello-pangea/dnd from 16.2.0 to 16.3.0
Bumps [@hello-pangea/dnd](https://github.com/hello-pangea/dnd) from 16.2.0 to 16.3.0.
- [Release notes](https://github.com/hello-pangea/dnd/releases)
- [Changelog](https://github.com/hello-pangea/dnd/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hello-pangea/dnd/compare/v16.2.0...v16.3.0)

---
updated-dependencies:
- dependency-name: "@hello-pangea/dnd"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 10:27:22 +00:00
dependabot[bot]
0cdee25b5b chore(deps): bump rehype-katex from 6.0.2 to 6.0.3
Bumps [rehype-katex](https://github.com/remarkjs/remark-math) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/remarkjs/remark-math/releases)
- [Commits](https://github.com/remarkjs/remark-math/compare/rehype-katex@6.0.2...rehype-katex@6.0.3)

---
updated-dependencies:
- dependency-name: rehype-katex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 10:27:11 +00:00
dependabot[bot]
92b0314c14 chore(deps-dev): bump prettier from 2.8.7 to 2.8.8
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.7 to 2.8.8.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.7...2.8.8)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 10:27:02 +00:00
Yidadaa
ad2bc7da96 Revert "feat: build universal app darwin bundle"
This reverts commit d8b606dc83.
2023-07-01 19:05:36 +08:00
Yidadaa
d8b606dc83 feat: build universal app darwin bundle 2023-07-01 18:54:12 +08:00
Yidadaa
5ce53dbcf4 fix: action ubuntu version 2023-07-01 18:39:48 +08:00
Yifei Zhang
c5c1a9ab3c Merge pull request #2209 from tdzz1102/main
Update Japanese translation
2023-07-01 18:32:12 +08:00
Yifei Zhang
564709aa98 Merge pull request #2213 from Yidadaa/bugfix-0701
feat: mac os arm64 bundle
2023-07-01 18:31:56 +08:00
Yidadaa
475158a145 feat: close #2194 add macos arm support 2023-07-01 18:28:47 +08:00
Yidadaa
829df56733 fix: #2195 correct macos icon size 2023-07-01 18:22:17 +08:00
Yidadaa
ee55f8790e fix: #2208 use global settings button dose not work 2023-07-01 18:16:06 +08:00
tdzz1102
6d19fb3909 update japanese translate 2023-07-01 12:28:08 +08:00
Yifei Zhang
9057712c8f Update chat.module.scss 2023-06-30 16:08:45 +08:00
Yifei Zhang
5d6e7de667 Merge pull request #2198 from YBY2020/main
perf : 代码细节优化
2023-06-30 14:35:41 +08:00
兀米
8f6f70879c Merge pull request #1 from YBY2020/YBY2020-patch-1
perf : 代码细节优化
2023-06-30 13:30:13 +08:00
兀米
3120087992 perf : 代码细节优化
大神, 学习代码过程中发现的一个小小的点,887行已定义`const isUser = message.role === "user";`,此处可直接用isUser
2023-06-30 12:00:03 +08:00
Yidadaa
0ec4cc223f fixup 2023-06-30 02:35:40 +08:00
Yidadaa
60c7be31b6 fixup 2023-06-30 02:05:17 +08:00
Yifei Zhang
2388f853c9 Merge pull request #2193 from Yidadaa/bugfix-0629
feat: improve ux & auto updater
2023-06-30 01:17:44 +08:00
Yidadaa
1e8d4763bb fix: tauri auto updater url 2023-06-30 01:15:01 +08:00
Yidadaa
be4834688d feat: close #2190 improve app auto updater 2023-06-30 00:39:54 +08:00
Yidadaa
3937dad6a6 feat: close #2187 improve chat actions ux 2023-06-30 00:39:54 +08:00
Yidadaa
463251dcc1 feat: allow to disable chunk building by setting DISABLE_CHUNK=1 2023-06-30 00:39:19 +08:00
Yifei Zhang
3adca26808 Update chat.module.scss 2023-06-29 11:03:13 +08:00
Yifei Zhang
97a8bb52d6 Update ui-lib.module.scss 2023-06-29 01:40:54 +08:00
Yifei Zhang
7748a980f9 Merge pull request #2185 from Yidadaa/bugfix-0628
feat: many ux improvments
2023-06-29 01:32:57 +08:00
Yidadaa
b044e274aa feat: close #2136 click avatar to edit message 2023-06-29 01:31:27 +08:00
Yidadaa
6c3d4a11cc feat: close #2141 danger zone 2023-06-29 01:09:51 +08:00
Yidadaa
98afd5516b feat: close #1994 add clipboard write api 2023-06-29 00:27:18 +08:00
Yidadaa
ea6926cad3 feat: replace window.prompt with showPrompt 2023-06-29 00:09:56 +08:00
Yidadaa
3298961748 feat: replace window.confirm with showConfirm 2023-06-28 23:40:39 +08:00
Yidadaa
0140f771c6 feat: close #2175 use default api host if endpoint is empty 2023-06-28 23:12:35 +08:00
Yidadaa
64c4f512ce fixup: #2149 chat action button style 2023-06-28 01:15:55 +08:00
Yidadaa
5b1d45c1a9 fix: #2149 try to fix chat action button style 2023-06-28 01:09:25 +08:00
Yifei Zhang
efbd1c15a9 Merge pull request #2174 from Yidadaa/bugfix-0627
fix: #2149 try to fix chat action button style
2023-06-28 01:03:19 +08:00
Yidadaa
bce74890dc fix: #2149 try to fix chat action button style 2023-06-28 01:02:56 +08:00
Yifei Zhang
eb93a43d13 Merge pull request #2173 from Yidadaa/bugfix-0627
feat: improve chat commands
2023-06-28 00:46:38 +08:00
Yidadaa
1dd75b63de feat: [WIP] support webdav 2023-06-28 00:34:01 +08:00
Yidadaa
6caf79121b feat: improve chat commands 2023-06-27 23:39:27 +08:00
Yifei Zhang
5cdd34a388 Merge pull request #2171 from Yidadaa/bugfix-0627
fix: #2149 try to fix ChatAction style on ios mobile
2023-06-27 23:31:04 +08:00
Yidadaa
38c8ee8cd2 feat: improve message item buttons style 2023-06-27 23:29:53 +08:00
Yidadaa
d5c33a1183 fix: #2149 try to fix ChatAction style on ios mobile 2023-06-27 23:07:12 +08:00
Yifei Zhang
058e28911a fix: #2135 disable cmd + arrow to change session 2023-06-26 18:29:46 +08:00
Yifei Zhang
25a3bb351d Merge pull request #2143 from yorunning/fix2
fix: alignment of mobile settings page title.
2023-06-26 13:27:00 +08:00
Yifei Zhang
1607640bed Merge pull request #2145 from Yidadaa/bugfix-0626
feat: #2144 improve input template
2023-06-26 13:21:48 +08:00
Yidadaa
5f0cda829f feat: #2144 improve input template 2023-06-26 13:18:59 +08:00
Yorun
b003a374b8 fix: alignment of mobile settings page title. 2023-06-26 04:16:06 +00:00
Yifei Zhang
5f7c262759 Merge pull request #2134 from Yidadaa/bugfix-0625
fix: input-range style
2023-06-26 01:51:55 +08:00
Yidadaa
f3ab6b27c9 fix: input-range style 2023-06-26 01:51:35 +08:00
Yifei Zhang
d62eb56886 Merge pull request #2133 from quzard/fix_pin_bug
Fix Pin Message
2023-06-26 01:43:20 +08:00
quzard
c2724c6aad Fix Pin Message: when userMessageIndex === 0, pinning a message was not possible. 2023-06-26 01:36:27 +08:00
Yifei Zhang
0cde1683b6 Update window.scss 2023-06-26 01:35:38 +08:00
Yifei Zhang
128f596f93 Update tauri.conf.json 2023-06-26 01:31:01 +08:00
Yifei Zhang
27fae8eed0 Merge pull request #2132 from Yidadaa/bugfix-0625
feat: align chat page title center on mobile screen
2023-06-26 01:28:56 +08:00
Yidadaa
82ec4474c2 feat: align chat page title center on mobile screen 2023-06-26 01:27:36 +08:00
Yifei Zhang
0d85dae239 Merge pull request #2131 from Yidadaa/bugfix-0625
feat: close #1615 pin messages to contextual prompts
2023-06-26 00:40:07 +08:00
Yidadaa
7893693706 feat: close #1615 pin messages to contextual prompts 2023-06-26 00:21:58 +08:00
Yidadaa
25ce6af36e feat: new chat message actions style 2023-06-25 23:53:22 +08:00
Yifei Zhang
c0269254e8 Merge pull request #2129 from Constaline/main
feat: add "Hide_Balance_Query" environment variable
2023-06-25 22:01:27 +08:00
Constaline
2f2aefd48e fix: typo 2023-06-25 21:33:22 +08:00
Constaline
c05de45d99 feat: add "Hide_Balance_Query" environment variable 2023-06-25 20:20:36 +08:00
Yifei Zhang
8f66da1128 Merge pull request #2119 from Yidadaa/bugfix-0624
chore: delete chat button position
2023-06-24 23:54:14 +08:00
Yidadaa
b5eaa8272b chore: delete chat button position 2023-06-24 23:53:37 +08:00
Yifei Zhang
7ee062e1de Merge pull request #2118 from Yidadaa/bugfix-0624
feat: #2013 #628 switch model button & chat commands
2023-06-24 23:40:54 +08:00
Yidadaa
8915af9b6d fixup 2023-06-24 23:39:38 +08:00
Yidadaa
ae1ef3215b feat: close #628 add chat commands 2023-06-24 23:38:11 +08:00
Yidadaa
5d06fa217c feat: close #2013 add switch model button to chat actions 2023-06-24 22:41:13 +08:00
Yidadaa
50b1f7db12 feat: right-click to copy message to user input 2023-06-24 22:32:46 +08:00
Yifei Zhang
fb82806956 Merge pull request #2117 from JunJD/优化useScrollToBottom
refactor: 优化 useScrollToBottom
2023-06-24 22:25:45 +08:00
Yifei Zhang
0658a93962 Merge pull request #2116 from Zizwar/main
Added support for Arabic language and right-to-left direction
2023-06-24 18:37:52 +08:00
Yifei Zhang
f33be37070 Update constant.ts 2023-06-24 16:50:05 +08:00
dingjunjie
9278d7f851 refactor: 优化 useScrollToBottom 2023-06-24 16:37:29 +08:00
Yifei Zhang
35ba898c97 Merge pull request #2112 from songxiaocheng/PR/history-message-count
fix: #2111 make "Attached Messages Count" working
2023-06-24 14:34:48 +08:00
Zizwar
15f8d13d81 Set markdown-body RTL for Arabic text 2023-06-24 05:53:58 +00:00
Zizwar
946e508e8f Support left to right in language arabic 2023-06-24 05:10:34 +00:00
Zizwar
35b4125b98 Added support Arabic language 2023-06-24 04:30:52 +00:00
Xiaocheng Song
91d8f9d73e fix: #2111 make "Attached Messages Count" working 2023-06-24 05:53:39 +08:00
Yifei Zhang
c636993989 Merge pull request #2110 from Yidadaa/bugfix-0623
doc: #695 how to use a proxy with password in docker
2023-06-24 00:30:17 +08:00
Yidadaa
65a24c16bc doc: #695 how to use a proxy with password in docker 2023-06-24 00:29:47 +08:00
Yifei Zhang
15ce114440 Merge pull request #2109 from Yidadaa/bugfix-0623
feat: close #1789 add user input template
2023-06-24 00:20:05 +08:00
Yidadaa
1722f75dcb fixup 2023-06-24 00:18:27 +08:00
Yidadaa
be597a551d feat: #close 1789 add user input template 2023-06-24 00:07:46 +08:00
Yifei Zhang
56bc945335 Merge pull request #2107 from Yidadaa/bugfix-0623
fix: #2087 chat box font size use global config
2023-06-23 20:58:09 +08:00
Yidadaa
fa9ceb5875 fix: #2087 chat box font size use global config 2023-06-23 20:57:43 +08:00
Yifei Zhang
0ccf5a16c7 Merge pull request #2106 from Yidadaa/bugfix-0623
feat: close #2025 just use a smaller to-bottom threshold
2023-06-23 20:53:28 +08:00
Yidadaa
93c666b03d feat: close #2025 just use a smaller to-bottom threshold 2023-06-23 20:53:08 +08:00
Yifei Zhang
d5c28b506c Update README.md 2023-06-23 20:21:41 +08:00
Yifei Zhang
3318f01aa1 Merge pull request #2099 from johan456789/patch-1
add explanation and link to FAQ
2023-06-23 13:20:02 +08:00
Tsung-Han Yu
1641019091 add explanation and link to FAQ 2023-06-23 05:54:39 +08:00
Yifei Zhang
3572969866 Merge pull request #2096 from Yidadaa/dependabot/cargo/src-tauri/openssl-0.10.55
chore(deps): bump openssl from 0.10.52 to 0.10.55 in /src-tauri
2023-06-22 17:43:41 +08:00
Yifei Zhang
ad1fe8762b Merge pull request #2092 from PaRaD1SE98/main
fix: migrate modelConfig state
2023-06-22 11:31:46 +08:00
dependabot[bot]
c9c3e32a78 chore(deps): bump openssl from 0.10.52 to 0.10.55 in /src-tauri
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.52 to 0.10.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.52...openssl-v0.10.55)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-21 22:57:40 +00:00
PaRaD1SE98
f4c99c9cf7 fix: migrate modelConfig state 2023-06-22 02:11:07 +09:00
Yifei Zhang
57447c24c8 Update tauri.conf.json 2023-06-21 15:36:17 +08:00
Yifei Zhang
514ed03378 Merge pull request #2065 from macvip/window-state
restore last window state (position and size) on startup
2023-06-21 02:30:25 +08:00
Yifei Zhang
078b039cf1 Merge pull request #2064 from mushan0x0/patch-1
fix: #2061 RequestInit TypeError
2023-06-20 18:04:24 +08:00
macvip
a120670785 restore last window state (position and size) on startup 2023-06-20 13:06:54 +08:00
mushan0x0
9c05d136f5 fix: #2061 RequestInit TypeError 2023-06-20 11:57:31 +08:00
Yifei Zhang
daeb8a67a3 Merge pull request #2048 from ilizhu/main
feat: i18n in message exporter
2023-06-20 02:45:48 +08:00
Yifei Zhang
c64888cccd Merge pull request #2051 from Yidadaa/dependabot/npm_and_yarn/next-13.4.6
chore(deps): bump next from 13.4.3 to 13.4.6
2023-06-20 02:40:23 +08:00
Yifei Zhang
6fc2637870 Merge pull request #2050 from Yidadaa/dependabot/npm_and_yarn/zustand-4.3.8
chore(deps): bump zustand from 4.3.6 to 4.3.8
2023-06-20 02:40:14 +08:00
Yifei Zhang
fc4df23075 Merge pull request #2053 from Yidadaa/dependabot/npm_and_yarn/lint-staged-13.2.2
chore(deps-dev): bump lint-staged from 13.2.0 to 13.2.2
2023-06-20 02:40:00 +08:00
Yifei Zhang
3ae502986b Merge pull request #2054 from Yidadaa/dependabot/npm_and_yarn/types/react-18.2.12
chore(deps-dev): bump @types/react from 18.0.31 to 18.2.12
2023-06-20 02:39:50 +08:00
Yifei Zhang
67f23d202a Merge pull request #2056 from Yidadaa/bugfix-0620
fix: #2055 should render mermaid completely
2023-06-20 02:38:59 +08:00
Yidadaa
3c38b9c93b fix: #2055 should render mermaid completely 2023-06-20 02:37:38 +08:00
dependabot[bot]
152a00baf5 chore(deps-dev): bump @types/react from 18.0.31 to 18.2.12
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.0.31 to 18.2.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 11:10:39 +00:00
dependabot[bot]
405d02023c chore(deps-dev): bump lint-staged from 13.2.0 to 13.2.2
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 13.2.0 to 13.2.2.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v13.2.0...v13.2.2)

---
updated-dependencies:
- dependency-name: lint-staged
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 11:10:15 +00:00
dependabot[bot]
4ba2d0d7e0 chore(deps): bump next from 13.4.3 to 13.4.6
Bumps [next](https://github.com/vercel/next.js) from 13.4.3 to 13.4.6.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.4.3...v13.4.6)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 11:09:00 +00:00
dependabot[bot]
38ce230cf5 chore(deps): bump zustand from 4.3.6 to 4.3.8
Bumps [zustand](https://github.com/pmndrs/zustand) from 4.3.6 to 4.3.8.
- [Release notes](https://github.com/pmndrs/zustand/releases)
- [Commits](https://github.com/pmndrs/zustand/compare/v4.3.6...v4.3.8)

---
updated-dependencies:
- dependency-name: zustand
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 11:06:59 +00:00
zhuge
c6696313bf 聊天记录导出格式为image时预览聊天信息项使用设置的语言 chat information items using the language in the settings when exporting chat records in image format 2023-06-19 16:16:09 +08:00
Yifei Zhang
89717c9b15 Update issue-translator.yml 2023-06-18 18:28:31 +08:00
Yifei Zhang
3aca699ad9 Create issue-translator.yml 2023-06-18 18:27:18 +08:00
Yifei Zhang
f3ec9f02eb Merge pull request #1807 from AngelLiang/feat/frequency_penalty
feat: Add frequency_penalty request parameter
2023-06-18 14:36:55 +08:00
Yifei Zhang
1c7f3e6057 Merge pull request #2028 from Yidadaa/bugfix-0618
fix: #1771 should not lose chat context when sumindex > n - count
2023-06-18 00:52:41 +08:00
Yidadaa
db63a5a670 fix: #1771 should not lose chat context when sumindex > n - count 2023-06-18 00:51:16 +08:00
Yifei Zhang
0a2cccfd6e Merge pull request #2018 from Yidadaa/bugfix-0617
feat: add nynorsk option
2023-06-17 01:20:06 +08:00
Yidadaa
113bd24796 feat: add nynorsk option 2023-06-17 01:03:06 +08:00
Yifei Zhang
374a27a72e Merge pull request #1065 from perandre/norwegian-translation
Add Norwegian translation
2023-06-17 00:33:26 +08:00
Xavier Barbosa
567eff9ef4 Remove deprecated keys 2023-06-16 16:00:57 +01:00
Xavier Barbosa
bfe33af03d Merge branch 'main' into norwegian-translation 2023-06-16 16:00:17 +01:00
Yifei Zhang
7ebc5eb70f Merge pull request #2002 from yeenbean/CVE-2023-2251
Remediation of CVE-2023-2251
2023-06-16 18:11:09 +08:00
Yifei Zhang
52287fd01d Merge pull request #2009 from taro-ishihara/i18n/japanese-translation
Fix Japanese language file
2023-06-16 18:10:56 +08:00
Yifei Zhang
e0f2bc2725 Merge pull request #2005 from Yidadaa/dependabot/npm_and_yarn/use-debounce-9.0.4
chore(deps): bump use-debounce from 9.0.3 to 9.0.4
2023-06-16 16:51:48 +08:00
Yifei Zhang
4355ec3b44 Merge pull request #2007 from Yidadaa/dependabot/npm_and_yarn/types/node-20.3.1
chore(deps-dev): bump @types/node from 18.15.11 to 20.3.1
2023-06-16 16:51:21 +08:00
Yifei Zhang
0c2f11ffd7 Merge pull request #2008 from Yidadaa/dependabot/npm_and_yarn/react-markdown-8.0.7
chore(deps): bump react-markdown from 8.0.6 to 8.0.7
2023-06-16 16:51:10 +08:00
Yifei Zhang
65e2f38fac Merge pull request #2004 from Yidadaa/dependabot/npm_and_yarn/mermaid-10.2.3
chore(deps): bump mermaid from 10.1.0 to 10.2.3
2023-06-16 16:50:51 +08:00
Taro Ishihara
aeaefbf8a9 Fix Japanese language file 2023-06-16 00:23:56 -07:00
dependabot[bot]
719cb3ae05 chore(deps): bump react-markdown from 8.0.6 to 8.0.7
Bumps [react-markdown](https://github.com/remarkjs/react-markdown) from 8.0.6 to 8.0.7.
- [Release notes](https://github.com/remarkjs/react-markdown/releases)
- [Changelog](https://github.com/remarkjs/react-markdown/blob/main/changelog.md)
- [Commits](https://github.com/remarkjs/react-markdown/compare/8.0.6...8.0.7)

---
updated-dependencies:
- dependency-name: react-markdown
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 07:22:32 +00:00
dependabot[bot]
08816c8f7d chore(deps-dev): bump @types/node from 18.15.11 to 20.3.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.15.11 to 20.3.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 07:22:15 +00:00
dependabot[bot]
1031203e3f chore(deps): bump use-debounce from 9.0.3 to 9.0.4
Bumps [use-debounce](https://github.com/xnimorz/use-debounce) from 9.0.3 to 9.0.4.
- [Release notes](https://github.com/xnimorz/use-debounce/releases)
- [Changelog](https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xnimorz/use-debounce/compare/9.0.3...9.0.4)

---
updated-dependencies:
- dependency-name: use-debounce
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 07:21:24 +00:00
dependabot[bot]
63ef6e7d3d chore(deps): bump mermaid from 10.1.0 to 10.2.3
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 10.1.0 to 10.2.3.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/v10.1.0...v10.2.3)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 07:20:57 +00:00
Yifei Zhang
590c9bb978 Create dependabot.yml 2023-06-16 15:12:47 +08:00
Joshua
5055a8c7a3 yarn.lock also required for CVE-2023-2251 🙃 2023-06-15 23:54:53 -07:00
Joshua
284c3f27e7 Resolves found vuln CVE-2023-2251 2023-06-15 23:33:08 -07:00
Yifei Zhang
468f198a6b Update README.md 2023-06-16 01:42:51 +08:00
Yifei Zhang
b0c6f6d4d3 Update tauri.conf.json 2023-06-16 00:53:10 +08:00
Yidadaa
473061b4d5 fixup: add TAURI_KEY_PASSWORD secrets 2023-06-16 00:18:54 +08:00
Yifei Zhang
c4921f3da7 Merge pull request #1990 from Yidadaa/bugfix-0615
fix: #1982 should not fullscreen on standlone build
2023-06-16 00:02:17 +08:00
Yidadaa
91b871ef3b feat: try to add auto updater 2023-06-15 23:55:18 +08:00
Yidadaa
698be6671c feat: enable drag area for tauri apps 2023-06-15 23:20:14 +08:00
Yidadaa
47c546fafa fix: #1982 should not fullscreen on standlone build 2023-06-15 22:34:50 +08:00
Yifei Zhang
9d1a84858c Merge pull request #1973 from Jiacheng787/patch-1
fix: updating the array using push in zustand does not actually trigger component updates
2023-06-15 14:42:33 +08:00
Jiacheng Dong
4dd5bf71ea Update chat.ts 2023-06-15 13:59:38 +08:00
Yidadaa
ea253e3bae fixup: tauri app name 2023-06-15 11:01:37 +08:00
Jiacheng Dong
1d42e955fc fix: updating the array using push in zustand does not actually trigger component updates 2023-06-15 10:55:25 +08:00
Jiacheng Dong
e636d486f5 fix: updating the array using push in zustand does not actually trigger component updates 2023-06-15 10:23:01 +08:00
Yifei Zhang
818cc545eb Merge pull request #1970 from Yidadaa/app
feat: update app release workflow
2023-06-15 03:32:18 +08:00
Yidadaa
e7858495e6 feat: update app release workflow 2023-06-15 03:28:53 +08:00
Yifei Zhang
af3a273a56 Merge pull request #1969 from Yidadaa/app
feat: ready to release desktop app
2023-06-15 02:53:42 +08:00
Yidadaa
6264c02543 feat: add app logo and
release workflow
2023-06-15 02:49:08 +08:00
Yidadaa
80d5bfd7c0 feat: add app dev mode 2023-06-15 01:48:56 +08:00
Yifei Zhang
3fa8b0ad14 Merge pull request #1968 from Yidadaa/bugfix-0614
fix: #1954 lazy render bugs
2023-06-15 01:40:51 +08:00
Yidadaa
184a0b9481 fix: #1954 lazy render bugs 2023-06-15 01:34:21 +08:00
Yifei Zhang
4e80096ee4 Merge pull request #1967 from Yidadaa/bugfix-0614
fix: #1931 try to fix cors issues
2023-06-15 00:31:00 +08:00
Yidadaa
0fb775d71a fix: #1931 try to fix cors issues 2023-06-15 00:28:47 +08:00
Yidadaa
76fdd047e7 feat: new token count function 2023-06-15 00:14:38 +08:00
Yidadaa
8590750e4c feat: close #1960 add gpt-3.5-turbo-16k-0613 2023-06-14 23:22:59 +08:00
Yifei Zhang
590bd8e4bb Merge pull request #1946 from cesaryuan/fix-selection
fix: remove selection range when user blured
2023-06-14 15:42:24 +08:00
Cesaryuan
b4cb8c3d75 chore: translate comment to english 2023-06-14 14:40:51 +08:00
Cesaryuan
21aa015a79 fix: remove slection range when user blured 2023-06-14 14:30:08 +08:00
Yifei Zhang
dd5b9d420b Merge pull request #1928 from samelamin/fix_chat_time
fix date time bug on chat lists
2023-06-14 11:31:00 +08:00
Yifei Zhang
2593de5872 Merge pull request #1929 from suhipek/main
add new models
2023-06-14 10:29:00 +08:00
suhipek
d2ae740d5f add new models 2023-06-14 01:51:37 +08:00
samelamin
c56c6074e9 fix date time bug 2023-06-13 18:32:51 +01:00
Yifei Zhang
426ce7fd35 Merge pull request #1927 from Yidadaa/bugfix-0613
feat: #1000 ready to support client-side only
2023-06-14 00:48:32 +08:00
Yidadaa
2a191aacb7 fixup 2023-06-14 00:46:52 +08:00
Yidadaa
50cd33dbb2 feat: #1000 ready to support client-side only 2023-06-14 00:37:42 +08:00
Yifei Zhang
e6b49a60c0 Merge pull request #1921 from Yidadaa/bugfix-0612
fixup: #1762 optimize style on mobile screen
2023-06-13 03:04:38 +08:00
Yidadaa
a7e9356c16 fixup: #1762 optimize style on mobile screen 2023-06-13 03:04:09 +08:00
Yifei Zhang
58cc1c8482 Merge pull request #1919 from Yidadaa/bugfix-0612
feat: close #1762 add hover text for chat input actions
2023-06-13 02:29:56 +08:00
Yidadaa
88df4a2223 feat: close #1762 add hover text for chat input actions 2023-06-13 02:27:39 +08:00
Yifei Zhang
d046100875 Merge pull request #1917 from Yidadaa/bugfix-0612
feat: white url list for openai security
2023-06-13 00:49:15 +08:00
Yidadaa
0d4611052e feat: white url list for openai security 2023-06-13 00:39:29 +08:00
Yifei Zhang
bdb03e07fc Merge pull request #1868 from OldDream666/fix-auth-Security
修改 auth.tsx 输入框为password
2023-06-11 00:01:05 +08:00
Old Dream
1d790b9e8d 修改 auth.tsx 输入框为password 2023-06-09 12:39:42 +08:00
Yifei Zhang
24bf15af4f Merge pull request #1863 from Yidadaa/bugfix-0608
feat: just disable all ngnix buffering
2023-06-08 23:51:09 +08:00
Yidadaa
6410aa214e feat: just disable all ngnix buffer 2023-06-08 23:49:06 +08:00
Yidadaa
a023308d52 feat: #1000 client-side only and desktop app 2023-06-07 23:47:21 +08:00
Yifei Zhang
2516851056 Merge pull request #1844 from Yidadaa/bugfix-0607
feat: close #741 add auth page
2023-06-07 02:26:05 +08:00
Yidadaa
a3a77006ff fixup: #1815 req.body will be broken in edge runtime 2023-06-07 02:24:45 +08:00
Yidadaa
ebbd0128f1 feat: close #741 add auth page 2023-06-07 02:18:24 +08:00
Yifei Zhang
773641aa16 Merge pull request #1843 from Yidadaa/bugfix-0607
fix: #1685 clear context index should be recoverable
2023-06-07 01:19:46 +08:00
Yidadaa
57514e91b6 fix: #1685 clear context index should be recoverable 2023-06-07 01:18:05 +08:00
Yifei Zhang
a9d336baf5 Merge pull request #1842 from Yidadaa/bugfix-0607
fix: #1815 refuse to serve when disable gpt4
2023-06-07 01:07:17 +08:00
Yidadaa
37da759fd5 fix: #1815 refuse to serve when disable gpt4 2023-06-07 01:02:01 +08:00
Yifei Zhang
2ebc26953d Merge pull request #1823 from johnfelipe/main
translation doc to spanish
2023-06-06 14:53:51 +08:00
Yifei Zhang
c8ba72b185 Merge pull request #1829 from OldDream666/fix-docker-bug
fix docker-compose.yml bug
2023-06-06 14:51:52 +08:00
Old Dream
35fe6e2f54 fix docker-compose.yml bug 2023-06-06 07:01:04 +08:00
johnfelipe
f3d35e0ef3 translation doc to spanish 2023-06-05 08:52:23 -05:00
Yifei Zhang
68327907a9 Merge pull request #1813 from qingfengfenga/main
Fix docker-compose bug
2023-06-05 17:48:13 +08:00
qingfengfenga
44fe85b56d Fix docker-compose bug 2023-06-05 11:10:23 +08:00
AngelLiang
72cbb156ae feat: Add frequency_penalty request parameter 2023-06-04 16:05:35 +08:00
Yifei Zhang
5e274130a6 Update README.md 2023-06-02 11:23:25 +08:00
Yifei Zhang
94be03ec4f Merge pull request #1781 from yanCode/fix/css-top-actions
fix: [CSS] the position of top-action buttons
2023-06-01 10:57:45 +08:00
Yifei Zhang
6502855a70 Merge pull request #1782 from yanCode/fix/json-format
fix: the display format of json error
2023-06-01 10:56:47 +08:00
ShengYan, Zhang
6bbdaf7ab0 fix: the display format of json 2023-05-31 19:39:39 +08:00
ShengYan, Zhang
846e323840 fix: the position of top-action buttons 2023-05-31 15:45:12 +08:00
Yifei Zhang
c198c33778 Merge pull request #1780 from leviding/patch-1
fix: style typo error in home.module.scss
2023-05-31 14:50:13 +08:00
LeviDing
c1cc3d1d1f fix: style typo error in home.module.scss 2023-05-30 18:42:13 +08:00
Yifei Zhang
6b5e5a15d7 Merge pull request #1777 from yanCode/chore/cleanup
chore: remove unused code
2023-05-29 16:07:14 +08:00
ShengYan, Zhang
7ac03b4d89 chore: remove unused code 2023-05-29 09:54:02 +08:00
Yifei Zhang
6bd75fae33 Update README.md 2023-05-27 00:37:13 +08:00
Yifei Zhang
3cdaf62fa1 Update README.md 2023-05-27 00:36:02 +08:00
Yifei Zhang
9aea6c5585 Merge pull request #1754 from popcell/fix-immutable-header
fix: #1746 Can't modify immutable headers.
2023-05-25 18:32:30 +08:00
popcell
d533895637 fix: #1746 Can't modify immutable headers. 2023-05-25 17:50:46 +08:00
Yifei Zhang
aa74a74c5c Merge pull request #1742 from wsw2000/refactor/listitem-undefined-classname
refactor: Fix undefined className in ListItem
2023-05-25 16:40:51 +08:00
Yifei Zhang
46f0a256d7 Merge pull request #1750 from gtoxlili/fix-docker-logic
fix: Logical corrections & syntax errors
2023-05-25 16:36:45 +08:00
gtoxlili
e33ad07e16 fix: Logical corrections & syntax errors 2023-05-25 11:46:31 +08:00
Yifei Zhang
786d62905d Merge pull request #1749 from lunaflora/main
*docker file bug
2023-05-25 11:39:31 +08:00
lunaflora
75594b8fe5 *docker file bug 2023-05-25 11:31:45 +08:00
wsw
96d2f05eb7 refactor: Fix undefined className in ListItem 2023-05-25 01:13:19 +08:00
Yifei Zhang
887f93181c Merge pull request #1741 from Yidadaa/bugfix-0524
feat: share to ShareGPT
2023-05-25 01:08:47 +08:00
Yidadaa
9f4a80f6ae chore: update readme 2023-05-25 01:08:19 +08:00
Yidadaa
3e65ef3bea feat: share to ShareGPT 2023-05-25 01:04:37 +08:00
Yidadaa
4ca34e0436 fix: #1711 input range style in mobile screen 2023-05-24 23:21:18 +08:00
Yifei Zhang
bb3f6ee086 Merge pull request #1738 from gtoxlili/main
fix : specify the default hostname if docker builds with a proxy
2023-05-24 22:15:21 +08:00
Yifei Zhang
89f0a4875c Merge pull request #1736 from popcell/main
fix vercel X-Forwarded-For headers
2023-05-24 22:13:00 +08:00
gtoxlili
aa2be9b96c fix : specify the default hostname if docker builds with a proxy 2023-05-24 21:40:53 +08:00
gtoxlili
707c1a2f7e fix : specify the default hostname if docker builds with a proxy 2023-05-24 21:18:51 +08:00
gtoxlili
ed14a0029a fix : issues #1732 2023-05-24 21:08:32 +08:00
popcell
989661e4df fix vercel X-Forwarded-For headers 2023-05-24 20:12:41 +08:00
Yifei Zhang
8874c687d8 Merge pull request #1728 from yanCode/fix/steps
fix:  bug #1727
2023-05-24 20:04:30 +08:00
Yifei Zhang
58e1997bcb Merge pull request #1731 from jarieshan/main
Add Jailbreak Mask
2023-05-24 20:03:37 +08:00
jarieshan
3e3055d7df Add Jailbreak Mask 2023-05-24 17:33:04 +08:00
jarieshan
b68d6e9d1a Revert "Add Jailbreak Mask"
This reverts commit cf9d200b7c.
2023-05-24 17:23:43 +08:00
jarieshan
cf9d200b7c Add Jailbreak Mask 2023-05-24 17:19:27 +08:00
ShengYan, Zhang
e84da3089a fix state loss after user switches from steps 2023-05-24 14:44:06 +08:00
ShengYan, Zhang
fee38b8d13 fix: a few typos 2023-05-24 14:42:36 +08:00
Yifei Zhang
e0a69a9e57 Update issue templates 2023-05-24 12:01:32 +08:00
Yifei Zhang
0bdc95f9c4 Update README.md 2023-05-24 11:53:27 +08:00
Yifei Zhang
4c9e4e507c Merge pull request #1694 from PaRaD1SE98/PaRaD1SE98-patch-1
clean next.config.mjs
2023-05-23 16:27:52 +08:00
Yifei Zhang
adb50fe64f Merge pull request #1695 from PaRaD1SE98/PaRaD1SE98-patch-2
fix: use Select component
2023-05-23 16:27:42 +08:00
Yifei Zhang
e883aefcc3 Merge pull request #1700 from misitebao/main
chore: optimize images
2023-05-23 16:27:31 +08:00
imgbot[bot]
cd7e8bbd3e chore: optimize images (#1)
*Total -- 122.47kb -> 90.64kb (25.98%)

/public/android-chrome-512x512.png -- 23.67kb -> 8.61kb (63.61%)
/app/icons/chatgpt.png -- 9.37kb -> 3.59kb (61.73%)
/app/icons/bot.png -- 7.14kb -> 4.98kb (30.2%)
/app/icons/three-dots.svg -- 1.46kb -> 1.09kb (25.75%)
/public/apple-touch-icon.png -- 11.30kb -> 8.45kb (25.21%)
/public/android-chrome-192x192.png -- 12.39kb -> 9.60kb (22.51%)
/public/favicon-32x32.png -- 1.51kb -> 1.22kb (19.21%)
/app/icons/min.svg -- 2.41kb -> 2.04kb (15.2%)
/app/icons/max.svg -- 2.24kb -> 1.90kb (14.98%)
/app/icons/menu.svg -- 1.17kb -> 1.00kb (14.64%)
/app/icons/share.svg -- 0.72kb -> 0.63kb (12.84%)
/app/icons/add.svg -- 1.25kb -> 1.09kb (12.46%)
/app/icons/github.svg -- 1.97kb -> 1.76kb (10.68%)
/app/icons/brain.svg -- 1.81kb -> 1.64kb (9.42%)
/app/icons/black-bot.svg -- 4.17kb -> 3.89kb (6.66%)
/docs/images/icon.svg -- 4.17kb -> 3.89kb (6.57%)
/app/icons/bottom.svg -- 0.72kb -> 0.69kb (4.08%)
/app/icons/download.svg -- 1.70kb -> 1.64kb (3.96%)
/app/icons/left.svg -- 0.56kb -> 0.54kb (3.49%)
/app/icons/down.svg -- 0.56kb -> 0.54kb (3.48%)
/app/icons/mask.svg -- 2.13kb -> 2.06kb (3.21%)
/app/icons/export.svg -- 1.20kb -> 1.16kb (3.17%)
/app/icons/copy.svg -- 0.99kb -> 0.96kb (2.87%)
/app/icons/prompt.svg -- 1.23kb -> 1.20kb (2.38%)
/app/icons/plugin.svg -- 3.54kb -> 3.48kb (1.6%)
/app/icons/delete.svg -- 23.11kb -> 23.00kb (0.49%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2023-05-23 13:57:26 +08:00
parad1se_jp
99317f759b fix: use Select component 2023-05-23 01:57:45 +09:00
PaRaD1SE98
7ff6a8651a clean next.config.mjs
appDir option no longer needed start from nextjs13.4 https://nextjs.org/docs/app/api-reference/next-config-js/appDir
2023-05-23 01:38:22 +09:00
Yifei Zhang
4e1e1d4fef Merge pull request #1693 from Yidadaa/bugfix-0522
fix: #1668 #1681
2023-05-23 00:00:14 +08:00
Yidadaa
d34676c5b2 fixup 2023-05-22 23:59:11 +08:00
Yidadaa
4cf659c29b chore: wont send system info if user use a mask 2023-05-22 23:52:09 +08:00
Yidadaa
ec61a5b32d fix: #1681 replace svg icons with png icons 2023-05-22 23:49:11 +08:00
Yidadaa
58f726c602 fix: #1668 should not summarize twice 2023-05-22 23:12:26 +08:00
Yifei Zhang
bb0c3835f4 Merge pull request #1689 from Yidadaa/Yidadaa-patch-1
fix: #1688 wrong clear context index
2023-05-22 18:57:21 +08:00
Yifei Zhang
e9642c7505 fix: #1688 wrong clear context index 2023-05-22 18:56:19 +08:00
Yifei Zhang
f0b4ef5917 Merge pull request #1679 from Yidadaa/export
chore: mobile export image style
2023-05-22 01:25:49 +08:00
Yidadaa
1f12753c68 chore: mobile export image style 2023-05-22 01:23:08 +08:00
Yifei Zhang
0439d122a5 Merge pull request #1678 from Yidadaa/export
feat: close #580 export messages as image
2023-05-22 01:02:58 +08:00
Yidadaa
4dad7f2ab6 feat: close #580 export messages as image 2023-05-22 00:59:36 +08:00
Yifei Zhang
ce75dc502b Merge pull request #1665 from yanCode/fix/prompt-select
fix: bug #1662
2023-05-21 12:55:47 +08:00
ShengYan, Zhang
23f6c2e8c9 fix: bug #1662 2023-05-21 11:58:10 +08:00
Yifei Zhang
d3461dd69b Merge pull request #1660 from Yidadaa/bugfix-0520
feat: close #1382 only clear memory btn in chat config
2023-05-21 02:06:01 +08:00
Yidadaa
05b1b8b240 feat: close #1382 only clear memory btn in chat config 2023-05-21 02:04:30 +08:00
Yifei Zhang
3118ba4466 Merge pull request #1659 from Yidadaa/bugfix-0520
feat: close #1415 clear context button
2023-05-21 01:48:38 +08:00
Yidadaa
35cec0f1df fixup: i18n and icon minor changes 2023-05-21 01:44:59 +08:00
Yidadaa
a19d238483 feat: close #1415 clear context button 2023-05-21 01:28:09 +08:00
Yifei Zhang
a57fa2e9ad Merge pull request #1658 from Yidadaa/bugfix-0520
feat: scrollable mask lists in new-chat page
2023-05-21 00:07:42 +08:00
Yidadaa
c2b36cdffa feat: prevent browser to invoke basic auth popup 2023-05-21 00:06:28 +08:00
Yidadaa
600b1814a1 fix: wont show auth popup when receiving a 401 http code 2023-05-20 23:58:36 +08:00
Yidadaa
76e6957a8a fixup 2023-05-20 23:53:39 +08:00
Yifei Zhang
18df79ce00 Merge pull request #1657 from Yidadaa/revert-1648-not-to-detect-user-lang-in-node
Revert "Not to detect user lang when running in Node"
2023-05-20 23:49:24 +08:00
Yidadaa
f14b413b7c feat: scrollable mask lists in new-chat page 2023-05-20 23:49:10 +08:00
Yifei Zhang
d0e73bd6b2 Revert "Not to detect user lang when running in Node" 2023-05-20 23:44:35 +08:00
Yifei Zhang
f27b25a62e Merge pull request #1653 from Yidadaa/bugfix-0520
feat: close #1626 hide context prompts in mask config
2023-05-20 20:20:35 +08:00
Yidadaa
6d8c7ba140 feat: close #1626 hide context prompts in mask config 2023-05-20 20:08:17 +08:00
Yidadaa
af497c96ec fix: #1612 infinite loading 2023-05-20 19:58:12 +08:00
Yifei Zhang
697c7a8dfe Merge pull request #1648 from yaojingguo/not-to-detect-user-lang-in-node
Not to detect user lang when running in Node
2023-05-20 16:07:18 +08:00
Jingguo Yao
3f5a189591 Not to detect user lang when running in Node
Use DEFAULT_LANG with Node. Remove the logging on the server side:

[Lang] failed to detect user lang.
2023-05-20 11:06:54 +08:00
Yifei Zhang
bcb18ff2f4 Merge pull request #1644 from Yidadaa/bugfix0519
feat: close #1478 new chat use global config as default
2023-05-20 00:43:08 +08:00
Yidadaa
b1ba3df989 feat: close #1478 new chat use global config as default 2023-05-20 00:39:52 +08:00
Yidadaa
203ac0970d feat: #1640 support free gpt endpoint 2023-05-19 23:53:27 +08:00
Yifei Zhang
e5329dc28a Merge pull request #1636 from yanCode/fix/css-chat-name
fix: css on display chat names
2023-05-19 22:22:28 +08:00
ShengYan, Zhang
f8ef6278a5 fix: css on display chat names 2023-05-19 20:14:57 +08:00
Yifei Zhang
7f13a8d2bc feat: support fast chatgpt mobile models 2023-05-19 18:34:48 +08:00
Yifei Zhang
b0b078c0fb Merge pull request #1629 from Algustine/dev
fix: get real-ip instead of vercel edge network ip
2023-05-19 18:31:59 +08:00
Illusion
c282433095 fix: get real-ip instead of vercel edge network ip 2023-05-19 16:03:29 +08:00
Yifei Zhang
8d7f3bd215 Merge pull request #1622 from yanCode/fix/single-mask-import
fix: allow to import a single mask
2023-05-19 10:57:37 +08:00
ShengYan, Zhang
f6c268dc1e fix: allow to import a single mask 2023-05-19 10:39:34 +08:00
Per André Rønsen
5a10b2a75f Update no.ts with const no: LocaleType 2023-05-10 14:59:57 +01:00
Per André Rønsen
72b553efe8 Add files for Norwegian translation 2023-05-10 14:59:36 +01:00
192 changed files with 15402 additions and 3231 deletions

14
.babelrc Normal file
View File

@@ -0,0 +1,14 @@
{
"presets": [
[
"next/babel",
{
"preset-env": {
"targets": {
"browsers": ["> 0.25%, not dead"]
}
}
}
]
]
}

View File

@@ -15,9 +15,13 @@ BASE_URL=
# Specify OpenAI organization ID.(optional)
# Default: Empty
# If you do not want users to input their own API key, set this value to 1.
OPENAI_ORG_ID=
# (optional)
# Default: Empty
# If you do not want users to use GPT-4, set this value to 1.
DISABLE_GPT4=
# (optional)
# Default: Empty
# If you do not want users to input their own API key, set this value to 1.
@@ -25,5 +29,10 @@ HIDE_USER_API_KEY=
# (optional)
# Default: Empty
# If you do not want users to use GPT-4, set this value to 1.
DISABLE_GPT4=
# If you do want users to query balance, set this value to 1.
ENABLE_BALANCE_QUERY=
# (optional)
# Default: Empty
# If you want to disable parse settings from url, set this value to 1.
DISABLE_FAST_LINK=

View File

@@ -7,6 +7,10 @@ assignees: ''
---
> 为了提高交流效率,我们设立了官方 QQ 群和 QQ 频道,如果你在使用或者搭建过程中遇到了任何问题,请先第一时间加群或者频道咨询解决,除非是可以稳定复现的 Bug 或者较为有创意的功能建议,否则请不要随意往 Issue 区发送低质无意义帖子。
> [点击加入官方群聊](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724)
**这个功能与现有的问题有关吗?**
如果有关,请在此列出链接或者描述问题。

View File

@@ -7,9 +7,13 @@ assignees: ''
---
> 为了提高交流效率,我们设立了官方 QQ 群和 QQ 频道,如果你在使用或者搭建过程中遇到了任何问题,请先第一时间加群或者频道咨询解决,除非是可以稳定复现的 Bug 或者较为有创意的功能建议,否则请不要随意往 Issue 区发送低质无意义帖子。
> [点击加入官方群聊](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724)
**反馈须知**
⚠️ 注意:不遵循此模板的任何帖子都会被立即关闭。
⚠️ 注意:不遵循此模板的任何帖子都会被立即关闭,如果没有提供下方的信息,我们无法定位你的问题
请在下方中括号内输入 x 来表示你已经知晓相关内容。
- [ ] 我确认已经在 [常见问题](https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/docs/faq-cn.md) 中搜索了此次反馈的问题,没有找到解答;

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

105
.github/workflows/app.yml vendored Normal file
View File

@@ -0,0 +1,105 @@
name: Release App
on:
workflow_dispatch:
release:
types: [published]
jobs:
create-release:
permissions:
contents: write
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.create-release.outputs.result }}
steps:
- uses: actions/checkout@v3
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 16
- name: get version
run: echo "PACKAGE_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').package.version")" >> $GITHUB_ENV
- name: create release
id: create-release
uses: actions/github-script@v6
with:
script: |
const { data } = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo,
})
return data.id
build-tauri:
needs: create-release
permissions:
contents: write
strategy:
fail-fast: false
matrix:
config:
- os: ubuntu-latest
arch: x86_64
rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
arch: x86_64
rust_target: x86_64-apple-darwin
- os: macos-latest
arch: aarch64
rust_target: aarch64-apple-darwin
- os: windows-latest
arch: x86_64
rust_target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.config.os }}
steps:
- uses: actions/checkout@v3
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 16
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.config.rust_target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.config.rust_target }}
- name: install dependencies (ubuntu only)
if: matrix.config.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install frontend dependencies
run: yarn install # change this to npm or pnpm depending on which one you use
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
releaseId: ${{ needs.create-release.outputs.release_id }}
publish-release:
permissions:
contents: write
runs-on: ubuntu-latest
needs: [create-release, build-tauri]
steps:
- name: publish release
id: publish-release
uses: actions/github-script@v6
env:
release_id: ${{ needs.create-release.outputs.release_id }}
with:
script: |
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.release_id,
draft: false,
prerelease: false
})

15
.github/workflows/issue-translator.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Issue Translator
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: false
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically.

View File

@@ -35,6 +35,6 @@ jobs:
- name: Sync check
if: failure()
run: |
echo "::error::由于权限不足,导致同步失败(这是预期的行为),请前往仓库首页手动执行[Sync fork]。"
echo "::error::Due to insufficient permissions, synchronization failed (as expected). Please go to the repository homepage and manually perform [Sync fork]."
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次详细教程请查看https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/README_CN.md#%E6%89%93%E5%BC%80%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0"
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork. Please refer to the detailed tutorial for instructions: https://github.com/Yidadaa/ChatGPT-Next-Web#enable-automatic-updates"
exit 1

3
.gitignore vendored
View File

@@ -41,3 +41,6 @@ dev
# docker-compose env files
.env
*.key
*.key.pub

View File

@@ -41,6 +41,7 @@ COPY --from=builder /app/.next/server ./.next/server
EXPOSE 3000
CMD if [ -n "$PROXY_URL" ]; then \
export HOSTNAME="127.0.0.1"; \
protocol=$(echo $PROXY_URL | cut -d: -f1); \
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
port=$(echo $PROXY_URL | cut -d: -f3); \

88
LICENSE
View File

@@ -1,75 +1,21 @@
版权所有c<2023><Zhang Yifei>
MIT License
反996许可证版本1.0
Copyright (c) 2023 Zhang Yifei
在符合下列条件的情况下,
特此免费向任何得到本授权作品的副本(包括源代码、文件和/或相关内容,以下统称为“授权作品”
)的个人和法人实体授权:被授权个人或法人实体有权以任何目的处置授权作品,包括但不限于使
用、复制,修改,衍生利用、散布,发布和再许可:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. 个人或法人实体必须在许可作品的每个再散布或衍生副本上包含以上版权声明和本许可证,不
得自行修改。
2. 个人或法人实体必须严格遵守与个人实际所在地或个人出生地或归化地、或法人实体注册地或
经营地(以较严格者为准)的司法管辖区所有适用的与劳动和就业相关法律、法规、规则和
标准。如果该司法管辖区没有此类法律、法规、规章和标准或其法律、法规、规章和标准不可
执行,则个人或法人实体必须遵守国际劳工标准的核心公约。
3. 个人或法人不得以任何方式诱导或强迫其全职或兼职员工或其独立承包人以口头或书面形式同
意直接或间接限制、削弱或放弃其所拥有的,受相关与劳动和就业有关的法律、法规、规则和
标准保护的权利或补救措施,无论该等书面或口头协议是否被该司法管辖区的法律所承认,该
等个人或法人实体也不得以任何方法限制其雇员或独立承包人向版权持有人或监督许可证合规
情况的有关当局报告或投诉上述违反许可证的行为的权利。
该授权作品是"按原样"提供,不做任何明示或暗示的保证,包括但不限于对适销性、特定用途适用
性和非侵权性的保证。在任何情况下,无论是在合同诉讼、侵权诉讼或其他诉讼中,版权持有人均
不承担因本软件或本软件的使用或其他交易而产生、引起或与之相关的任何索赔、损害或其他责任。
------------------------- ENGLISH ------------------------------
Copyright (c) <2023> <Zhang Yifei>
Anti 996 License Version 1.0 (Draft)
Permission is hereby granted to any individual or legal entity obtaining a copy
of this licensed work (including the source code, documentation and/or related
items, hereinafter collectively referred to as the "licensed work"), free of
charge, to deal with the licensed work for any purpose, including without
limitation, the rights to use, reproduce, modify, prepare derivative works of,
publish, distribute and sublicense the licensed work, subject to the following
conditions:
1. The individual or the legal entity must conspicuously display, without
modification, this License on each redistributed or derivative copy of the
Licensed Work.
2. The individual or the legal entity must strictly comply with all applicable
laws, regulations, rules and standards of the jurisdiction relating to
labor and employment where the individual is physically located or where
the individual was born or naturalized; or where the legal entity is
registered or is operating (whichever is stricter). In case that the
jurisdiction has no such laws, regulations, rules and standards or its
laws, regulations, rules and standards are unenforceable, the individual
or the legal entity are required to comply with Core International Labor
Standards.
3. The individual or the legal entity shall not induce or force its
employee(s), whether full-time or part-time, or its independent
contractor(s), in any methods, to agree in oral or written form,
to directly or indirectly restrict, weaken or relinquish his or
her rights or remedies under such laws, regulations, rules and
standards relating to labor and employment as mentioned above,
no matter whether such written or oral agreement are enforceable
under the laws of the said jurisdiction, nor shall such individual
or the legal entity limit, in any methods, the rights of its employee(s)
or independent contractor(s) from reporting or complaining to the copyright
holder or relevant authorities monitoring the compliance of the license
about its violation(s) of the said license.
THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION
WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

141
README.md
View File

@@ -5,13 +5,25 @@
English / [简体中文](./README_CN.md)
One-Click to deploy well-designed ChatGPT web UI on Vercel.
One-Click to get well-designed cross-platform ChatGPT web UI.
一键免费部署你的私人 ChatGPT 网页应用。
一键免费部署你的跨平台私人 ChatGPT 应用。
[Demo](https://chatgpt.nextweb.fun/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
[![Web][Web-image]][web-url]
[![Windows][Windows-image]][download-url]
[![MacOS][MacOS-image]][download-url]
[![Linux][Linux-image]][download-url]
[演示](https://chatgpt.nextweb.fun/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/assets/16968934/3ff423d5-5703-4772-8b6d-abec7eec3a4b) / [QQ 频道](https://github.com/Yidadaa/ChatGPT-Next-Web/assets/16968934/debfbee7-e682-4814-a601-f4403dac6d1d) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Twitter](https://twitter.com/mortiest_ricky) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
[网页版](https://chatgpt.nextweb.fun/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
[web-url]: https://chatgpt.nextweb.fun
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
[Windows-image]: https://img.shields.io/badge/-Windows-blue?logo=windows
[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%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
@@ -24,6 +36,8 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
## Features
- **Deploy for free with one-click** on Vercel in under 1 minute
- Compact client (~5MB) on Linux/Windows/MacOS, [download it now](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- Fully compatible with self-deployed llms, recommended for use with [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) or [LocalAI](https://github.com/go-skynet/LocalAI)
- Privacy first, all data stored locally in the browser
- Markdown support: LaTex, mermaid, code highlight, etc.
- Responsive design, dark mode and PWA
@@ -31,30 +45,29 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
- New in v2: create, share and debug your chat tools with prompt templates (mask)
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
- 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, 한국어
- I18n: English, 简体中文, 繁体中文, 日本語, Français, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština, 한국어, Indonesia
## Roadmap
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- [x] User Prompt: user can edit and save custom prompts to prompt list
- [x] Prompt Template: create a new chat with pre-defined in-context prompts [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
- [ ] Share as image, share to ShareGPT
- [ ] Desktop App with tauri
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
- [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)
### Not in Plan
- User login, accounts, cloud sync
- UI text customize
## What's New
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
- 🚀 v2.7 let's share conversations as image, or share to ShareGPT!
- 🚀 v2.8 now we have a client that runs across all platforms!
- 🚀 v2.9.11 you can use azure endpoint now.
## 主要功能
- 在 1 分钟内使用 Vercel **免费一键部署**
- 提供体积极小(~5MB的跨平台客户端Linux/Windows/MacOS, [下载地址](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- 完整的 Markdown 支持LaTex 公式、Mermaid 流程图、代码高亮等等
- 精心设计的 UI响应式设计支持深色模式支持 PWA
- 极快的首屏加载速度(~100kb支持流式响应
@@ -70,20 +83,18 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
- [x] 允许用户自行编辑内置 Prompt 列表
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
- [ ] 分享为图片,分享到 ShareGPT
- [ ] 使用 tauri 打包桌面应用
- [ ] 支持自部署的大语言模型
- [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)
### 不会开发的功能
- 界面文字自定义
- 用户登录、账号管理、消息云同步
## 最新动态
- 🚀 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
@@ -126,7 +137,7 @@ After forking the project, due to the limitations imposed by GitHub, you need to
If you want to update instantly, you can check out the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code.
You can star or watch this project or follow author to get release notifictions in time.
You can star or watch this project or follow author to get release notifications in time.
## Access Password
@@ -144,14 +155,14 @@ After adding or modifying this environment variable, please redeploy the project
> [简体中文 > 如何配置 api key、访问密码、接口代理](./README_CN.md#环境变量)
### `CODE` (optional)
Access password, separated by comma.
### `OPENAI_API_KEY` (required)
Your openai api key.
### `CODE` (optional)
Access passsword, separated by comma.
### `BASE_URL` (optional)
> Default: `https://api.openai.com`
@@ -164,6 +175,20 @@ Override openai api request base url.
Specify OpenAI organization ID.
### `AZURE_URL` (optional)
> Example: https://{azure-resource-url}/openai/deployments/{deploy-name}
Azure deploy url.
### `AZURE_API_KEY` (optional)
Azure Api Key.
### `AZURE_API_VERSION` (optional)
Azure Api Version, find it at [Azure Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions).
### `HIDE_USER_API_KEY` (optional)
> Default: Empty
@@ -176,6 +201,29 @@ If you do not want users to input their own API key, set this value to 1.
If you do not want users to use GPT-4, set this value to 1.
### `ENABLE_BALANCE_QUERY` (optional)
> Default: Empty
If you do want users to query balance, set this value to 1, or you should set it to 0.
### `DISABLE_FAST_LINK` (optional)
> Default: Empty
If you want to disable parse settings from url, set this to 1.
### `CUSTOM_MODELS` (optional)
> Default: Empty
> Example: `+llama,+claude-2,-gpt-3.5-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list.
To control custom models, use `+` to add a custom model, use `-` to hide a model, separated by comma.
## Requirements
NodeJS >= 18, Docker >= 20
## Development
> [简体中文 > 如何进行二次开发](./README_CN.md#开发)
@@ -186,6 +234,9 @@ Before starting development, you must create a new `.env.local` file at project
```
OPENAI_API_KEY=<your api key here>
# if you are not able to access openai service, use this BASE_URL
BASE_URL=https://chatgpt1.nextweb.fun/api/proxy
```
### Local Development
@@ -208,8 +259,8 @@ yarn dev
docker pull yidadaa/chatgpt-next-web
docker run -d -p 3000:3000 \
-e OPENAI_API_KEY="sk-xxxx" \
-e CODE="your-password" \
-e OPENAI_API_KEY=sk-xxxx \
-e CODE=your-password \
yidadaa/chatgpt-next-web
```
@@ -217,24 +268,48 @@ You can start service behind a proxy:
```shell
docker run -d -p 3000:3000 \
-e OPENAI_API_KEY="sk-xxxx" \
-e CODE="your-password" \
-e PROXY_URL="http://localhost:7890" \
-e OPENAI_API_KEY=sk-xxxx \
-e CODE=your-password \
-e PROXY_URL=http://localhost:7890 \
yidadaa/chatgpt-next-web
```
If your proxy needs password, use:
```shell
-e PROXY_URL="http://127.0.0.1:7890 user pass"
```
### Shell
```shell
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
```
## Synchronizing Chat Records (UpStash)
| [简体中文](./docs/synchronise-chat-logs-cn.md) | [English](./docs/synchronise-chat-logs-en.md) | [Italiano](./docs/synchronise-chat-logs-es.md) | [日本語](./docs/synchronise-chat-logs-ja.md) | [한국어](./docs/synchronise-chat-logs-ko.md)
## Documentation
> Please go to the [docs][./docs] directory for more documentation instructions.
- [Deploy with cloudflare (Deprecated)](./docs/cloudflare-pages-en.md)
- [Frequent Ask Questions](./docs/faq-en.md)
- [How to add a new translation](./docs/translation.md)
- [How to use Vercel (No English)](./docs/vercel-cn.md)
- [User Manual (Only Chinese, WIP)](./docs/user-manual-cn.md)
## Screenshots
![Settings](./docs/images/settings.png)
![More](./docs/images/more.png)
## Translation
If you want to add a new translation, read this [document](./docs/translation.md).
## Donation
[Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
@@ -266,6 +341,8 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
[@Sha1rholder](https://github.com/Sha1rholder)
[@AnsonHyq](https://github.com/AnsonHyq)
[@synwith](https://github.com/synwith)
[@piksonGit](https://github.com/piksonGit)
[@ouyangzhiping](https://github.com/ouyangzhiping)
### Contributor
@@ -273,4 +350,4 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
## LICENSE
[Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)
[MIT](https://opensource.org/license/mit/)

View File

@@ -90,6 +90,20 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
指定 OpenAI 中的组织 ID。
### `AZURE_URL` (可选)
> 形如https://{azure-resource-url}/openai/deployments/{deploy-name}
Azure 部署地址。
### `AZURE_API_KEY` (可选)
Azure 密钥。
### `AZURE_API_VERSION` (可选)
Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)。
### `HIDE_USER_API_KEY` (可选)
如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。
@@ -98,9 +112,21 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
如果你不想让用户使用 GPT-4将此环境变量设置为 1 即可。
## 开发
### `ENABLE_BALANCE_QUERY` (可选)
> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器
如果你想启用余额查询功能,将此环境变量设置为 1 即可
### `DISABLE_FAST_LINK` (可选)
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
### `CUSTOM_MODELS` (可选)
> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`。
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,用英文逗号隔开。
## 开发
点击下方按钮,开始二次开发:
@@ -110,13 +136,16 @@ OpenAI 接口代理 URL如果你手动配置了 openai 接口代理,请填
```
OPENAI_API_KEY=<your api key here>
# 中国大陆用户,可以使用本项目自带的代理进行开发,你也可以自由选择其他代理地址
BASE_URL=https://ab.nextweb.fun/api/proxy
```
### 本地开发
1. 安装 nodejs 18 和 yarn具体细节请询问 ChatGPT
2. 执行 `yarn install && yarn dev` 即可。⚠️ 注意:此命令仅用于本地开发,不要用于部署!
3. 如果你想本地部署,请使用 `yarn install && yarn start` 命令,你可以配合 pm2 来守护进程,防止被杀死,详情询问 ChatGPT。
3. 如果你想本地部署,请使用 `yarn install && yarn build && yarn start` 命令,你可以配合 pm2 来守护进程,防止被杀死,详情询问 ChatGPT。
## 部署
@@ -130,8 +159,8 @@ OPENAI_API_KEY=<your api key here>
docker pull yidadaa/chatgpt-next-web
docker run -d -p 3000:3000 \
-e OPENAI_API_KEY="sk-xxxx" \
-e CODE="页面访问密码" \
-e OPENAI_API_KEY=sk-xxxx \
-e CODE=页面访问密码 \
yidadaa/chatgpt-next-web
```
@@ -139,13 +168,19 @@ docker run -d -p 3000:3000 \
```shell
docker run -d -p 3000:3000 \
-e OPENAI_API_KEY="sk-xxxx" \
-e CODE="页面访问密码" \
-e OPENAI_API_KEY=sk-xxxx \
-e CODE=页面访问密码 \
--net=host \
-e PROXY_URL="http://127.0.0.1:7890" \
-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 环境变量=环境变量值` 来指定。
### 本地部署
@@ -168,8 +203,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
[见项目贡献者列表](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
### 相关项目
- [one-api](https://github.com/songquanpeng/one-api): 一站式大模型额度管理平台,支持市面上所有主流大语言模型
## 开源协议
> 反对 996从我开始。
[Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)
[MIT](https://opensource.org/license/mit/)

View File

@@ -28,7 +28,7 @@ export function auth(req: NextRequest) {
const authToken = req.headers.get("Authorization") ?? "";
// check if it is openai api key or user token
const { accessCode, apiKey: token } = parseApiKey(authToken);
const { accessCode, apiKey } = parseApiKey(authToken);
const hashedCode = md5.hash(accessCode ?? "").trim();
@@ -39,7 +39,7 @@ export function auth(req: NextRequest) {
console.log("[User IP] ", getIP(req));
console.log("[Time] ", new Date().toLocaleString());
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
return {
error: true,
msg: !accessCode ? "empty access code" : "wrong access code",
@@ -47,17 +47,19 @@ export function auth(req: NextRequest) {
}
// if user does not provide an api key, inject system api key
if (!token) {
const apiKey = serverConfig.apiKey;
if (apiKey) {
if (!apiKey) {
const serverApiKey = serverConfig.isAzure
? serverConfig.azureApiKey
: serverConfig.apiKey;
if (serverApiKey) {
console.log("[Auth] use system api key");
req.headers.set("Authorization", `Bearer ${apiKey}`);
req.headers.set(
"Authorization",
`${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
);
} else {
console.log("[Auth] admin did not provide an api key");
return {
error: true,
msg: "admin did not provide an api key",
};
}
} else {
console.log("[Auth] use user api key");

View File

@@ -1,55 +1,116 @@
import { NextRequest } from "next/server";
import { NextRequest, NextResponse } from "next/server";
import { getServerSideConfig } from "../config/server";
import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
import { collectModelTable } from "../utils/model";
import { makeAzurePath } from "../azure";
const OPENAI_URL = "api.openai.com";
const DEFAULT_PROTOCOL = "https";
const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
const serverConfig = getServerSideConfig();
export async function requestOpenai(req: NextRequest) {
const controller = new AbortController();
const authValue = req.headers.get("Authorization") ?? "";
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization";
let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
"/api/openai/",
"",
);
let baseUrl = BASE_URL;
let baseUrl =
serverConfig.azureUrl ?? serverConfig.baseUrl ?? OPENAI_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `${PROTOCOL}://${baseUrl}`;
baseUrl = `https://${baseUrl}`;
}
console.log("[Proxy] ", openaiPath);
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl);
console.log("[Org ID]", serverConfig.openaiOrgId);
if (process.env.OPENAI_ORG_ID) {
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);
if (serverConfig.isAzure) {
if (!serverConfig.azureApiVersion) {
return NextResponse.json({
error: true,
message: `missing AZURE_API_VERSION in server env vars`,
});
}
path = makeAzurePath(path, serverConfig.azureApiVersion);
}
const timeoutId = setTimeout(() => {
controller.abort();
}, 10 * 60 * 1000);
const fetchUrl = `${baseUrl}/${path}`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
[authHeaderName]: authValue,
...(serverConfig.openaiOrgId && {
"OpenAI-Organization": serverConfig.openaiOrgId,
}),
},
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,
};
// #1815 try to refuse gpt4 request
if (serverConfig.customModels && req.body) {
try {
const modelTable = collectModelTable(
DEFAULT_MODELS,
serverConfig.customModels,
);
const clonedBody = await req.text();
fetchOptions.body = clonedBody;
const jsonBody = JSON.parse(clonedBody) as { model?: string };
// not undefined and is false
if (modelTable[jsonBody?.model ?? ""] === false) {
return NextResponse.json(
{
error: true,
message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
},
);
}
} catch (e) {
console.error("[OpenAI] gpt4 filter", e);
}
}
try {
return await fetch(`${baseUrl}/${openaiPath}`, {
headers: {
"Content-Type": "application/json",
Authorization: authValue,
...(process.env.OPENAI_ORG_ID && {
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
}),
},
cache: "no-store",
method: req.method,
body: req.body,
signal: controller.signal,
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,
});
} catch (err: unknown) {
if (err instanceof Error && err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
throw err;
}
} finally {
clearTimeout(timeoutId);
}

View File

@@ -4,12 +4,15 @@ import { getServerSideConfig } from "../../config/server";
const serverConfig = getServerSideConfig();
// Danger! Don not write any secret value here!
// Danger! Do not hard code any secret value here!
// 警告!不要在这里写入任何敏感信息!
const DANGER_CONFIG = {
needCode: serverConfig.needCode,
hideUserApiKey: serverConfig.hideUserApiKey,
enableGPT4: serverConfig.enableGPT4,
disableGPT4: serverConfig.disableGPT4,
hideBalanceQuery: serverConfig.hideBalanceQuery,
disableFastLink: serverConfig.disableFastLink,
customModels: serverConfig.customModels,
};
declare global {

View File

@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from "next/server";
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
const [protocol, ...subpath] = params.path;
const targetUrl = `${protocol}://${subpath.join("/")}`;
const method = req.headers.get("method") ?? undefined;
const shouldNotHaveBody = ["get", "head"].includes(
method?.toLowerCase() ?? "",
);
const fetchOptions: RequestInit = {
headers: {
authorization: req.headers.get("authorization") ?? "",
},
body: shouldNotHaveBody ? null : req.body,
method,
// @ts-ignore
duplex: "half",
};
const fetchResult = await fetch(targetUrl, fetchOptions);
console.log("[Any Proxy]", targetUrl, {
status: fetchResult.status,
statusText: fetchResult.statusText,
});
return fetchResult;
}
export const POST = handle;
export const GET = handle;
export const OPTIONS = handle;
export const runtime = "nodejs";

View File

@@ -1,14 +1,50 @@
import { type OpenAIListModelResponse } from "@/app/client/platforms/openai";
import { getServerSideConfig } from "@/app/config/server";
import { OpenaiPath } from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "../../auth";
import { requestOpenai } from "../../common";
const ALLOWD_PATH = new Set(Object.values(OpenaiPath));
function getModels(remoteModelRes: OpenAIListModelResponse) {
const config = getServerSideConfig();
if (config.disableGPT4) {
remoteModelRes.data = remoteModelRes.data.filter(
(m) => !m.id.startsWith("gpt-4"),
);
}
return remoteModelRes;
}
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
console.log("[OpenAI Route] params ", params);
if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
const subpath = params.path.join("/");
if (!ALLOWD_PATH.has(subpath)) {
console.log("[OpenAI Route] forbidden path ", subpath);
return NextResponse.json(
{
error: true,
msg: "you are not allowed to request " + subpath,
},
{
status: 403,
},
);
}
const authResult = auth(req);
if (authResult.error) {
return NextResponse.json(authResult, {
@@ -17,7 +53,18 @@ async function handle(
}
try {
return await requestOpenai(req);
const response = await requestOpenai(req);
// list models
if (subpath === OpenaiPath.ListModelPath && response.status === 200) {
const resJson = (await response.json()) as OpenAIListModelResponse;
const availableModels = getModels(resJson);
return NextResponse.json(availableModels, {
status: response.status,
});
}
return response;
} catch (e) {
console.error("[OpenAI] ", e);
return NextResponse.json(prettyObject(e));

9
app/azure.ts Normal file
View File

@@ -0,0 +1,9 @@
export function makeAzurePath(path: string, apiVersion: string) {
// should omit /v1 prefix
path = path.replaceAll("v1/", "");
// should add api-key to query string
path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`;
return path;
}

View File

@@ -1,5 +1,6 @@
import { ACCESS_CODE_PREFIX } from "../constant";
import { ModelConfig, ModelType, useAccessStore } from "../store";
import { getClientConfig } from "../config/client";
import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant";
import { ChatMessage, ModelType, useAccessStore } from "../store";
import { ChatGPTApi } from "./platforms/openai";
export const ROLES = ["system", "user", "assistant"] as const;
@@ -37,9 +38,36 @@ export interface LLMUsage {
total: number;
}
export interface LLMModel {
name: string;
available: boolean;
}
export abstract class LLMApi {
abstract chat(options: ChatOptions): Promise<void>;
abstract usage(): Promise<LLMUsage>;
abstract models(): Promise<LLMModel[]>;
}
type ProviderName = "openai" | "azure" | "claude" | "palm";
interface Model {
name: string;
provider: ProviderName;
ctxlen: number;
}
interface ChatProvider {
name: ProviderName;
apiConfig: {
baseUrl: string;
apiKey: string;
summaryModel: Model;
};
models: Model[];
chat: () => void;
usage: () => void;
}
export class ClientApi {
@@ -54,27 +82,71 @@ export class ClientApi {
prompts() {}
masks() {}
async share(messages: ChatMessage[], avatarUrl: string | null = null) {
const msgs = messages
.map((m) => ({
from: m.role === "user" ? "human" : "gpt",
value: m.content,
}))
.concat([
{
from: "human",
value:
"Share from [ChatGPT Next Web]: https://github.com/Yidadaa/ChatGPT-Next-Web",
},
]);
// 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用
// Please do not modify this message
console.log("[Share]", messages, msgs);
const clientConfig = getClientConfig();
const proxyUrl = "/sharegpt";
const rawUrl = "https://sharegpt.com/api/conversations";
const shareUrl = clientConfig?.isApp ? rawUrl : proxyUrl;
const res = await fetch(shareUrl, {
body: JSON.stringify({
avatarUrl,
items: msgs,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
const resJson = await res.json();
console.log("[Share]", resJson);
if (resJson.id) {
return `https://shareg.pt/${resJson.id}`;
}
}
}
export const api = new ClientApi();
export function getHeaders() {
const accessStore = useAccessStore.getState();
let headers: Record<string, string> = {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"x-requested-with": "XMLHttpRequest",
};
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
const isAzure = accessStore.provider === ServiceProvider.Azure;
const authHeader = isAzure ? "api-key" : "Authorization";
const apiKey = isAzure ? accessStore.azureApiKey : accessStore.openaiApiKey;
const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
const validString = (x: string) => x && x.length > 0;
// use user's api key first
if (validString(accessStore.token)) {
headers.Authorization = makeBearer(accessStore.token);
if (validString(apiKey)) {
headers[authHeader] = makeBearer(apiKey);
} else if (
accessStore.enabledAccessControl() &&
validString(accessStore.accessCode)
) {
headers.Authorization = makeBearer(
headers[authHeader] = makeBearer(
ACCESS_CODE_PREFIX + accessStore.accessCode,
);
}

View File

@@ -3,17 +3,17 @@ export const ChatControllerPool = {
controllers: {} as Record<string, AbortController>,
addController(
sessionIndex: number,
messageId: number,
sessionId: string,
messageId: string,
controller: AbortController,
) {
const key = this.key(sessionIndex, messageId);
const key = this.key(sessionId, messageId);
this.controllers[key] = controller;
return key;
},
stop(sessionIndex: number, messageId: number) {
const key = this.key(sessionIndex, messageId);
stop(sessionId: string, messageId: string) {
const key = this.key(sessionId, messageId);
const controller = this.controllers[key];
controller?.abort();
},
@@ -26,12 +26,12 @@ export const ChatControllerPool = {
return Object.values(this.controllers).length > 0;
},
remove(sessionIndex: number, messageId: number) {
const key = this.key(sessionIndex, messageId);
remove(sessionId: string, messageId: string) {
const key = this.key(sessionId, messageId);
delete this.controllers[key];
},
key(sessionIndex: number, messageIndex: number) {
return `${sessionIndex},${messageIndex}`;
key(sessionId: string, messageIndex: string) {
return `${sessionId},${messageIndex}`;
},
};

View File

@@ -1,25 +1,65 @@
import { REQUEST_TIMEOUT_MS } from "@/app/constant";
import {
ApiPath,
DEFAULT_API_HOST,
DEFAULT_MODELS,
OpenaiPath,
REQUEST_TIMEOUT_MS,
ServiceProvider,
} from "@/app/constant";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { ChatOptions, getHeaders, LLMApi, LLMUsage } from "../api";
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
import Locale from "../../locales";
import {
EventStreamContentType,
fetchEventSource,
} from "@microsoft/fetch-event-source";
} from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
import { makeAzurePath } from "@/app/azure";
export interface OpenAIListModelResponse {
object: string;
data: Array<{
id: string;
object: string;
root: string;
}>;
}
export class ChatGPTApi implements LLMApi {
public ChatPath = "v1/chat/completions";
public UsagePath = "dashboard/billing/usage";
public SubsPath = "dashboard/billing/subscription";
private disableListModels = true;
path(path: string): string {
let openaiUrl = useAccessStore.getState().openaiUrl;
if (openaiUrl.endsWith("/")) {
openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1);
const accessStore = useAccessStore.getState();
const isAzure = accessStore.provider === ServiceProvider.Azure;
if (isAzure && !accessStore.isValidAzure()) {
throw Error(
"incomplete azure config, please check it in your settings page",
);
}
return [openaiUrl, path].join("/");
let baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
if (baseUrl.length === 0) {
const isApp = !!getClientConfig()?.isApp;
baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
baseUrl = "https://" + baseUrl;
}
if (isAzure) {
path = makeAzurePath(path, accessStore.azureApiVersion);
}
return [baseUrl, path].join("/");
}
extractMessage(res: any) {
@@ -46,6 +86,10 @@ export class ChatGPTApi implements LLMApi {
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);
@@ -55,7 +99,7 @@ export class ChatGPTApi implements LLMApi {
options.onController?.(controller);
try {
const chatPath = this.path(this.ChatPath);
const chatPath = this.path(OpenaiPath.ChatPath);
const chatPayload = {
method: "POST",
body: JSON.stringify(requestPayload),
@@ -130,14 +174,20 @@ export class ChatGPTApi implements LLMApi {
}
const text = msg.data;
try {
const json = JSON.parse(text);
const delta = json.choices[0].delta.content;
const json = JSON.parse(text) as {
choices: Array<{
delta: {
content: string;
};
}>;
};
const delta = json.choices[0]?.delta?.content;
if (delta) {
responseText += delta;
options.onUpdate?.(responseText, delta);
}
} catch (e) {
console.error("[Request] parse error", text, msg);
console.error("[Request] parse error", text);
}
},
onclose() {
@@ -145,6 +195,7 @@ export class ChatGPTApi implements LLMApi {
},
onerror(e) {
options.onError?.(e);
throw e;
},
openWhenHidden: true,
});
@@ -157,7 +208,7 @@ export class ChatGPTApi implements LLMApi {
options.onFinish(message);
}
} catch (e) {
console.log("[Request] failed to make a chat reqeust", e);
console.log("[Request] failed to make a chat request", e);
options.onError?.(e as Error);
}
}
@@ -176,14 +227,14 @@ export class ChatGPTApi implements LLMApi {
const [used, subs] = await Promise.all([
fetch(
this.path(
`${this.UsagePath}?start_date=${startDate}&end_date=${endDate}`,
`${OpenaiPath.UsagePath}?start_date=${startDate}&end_date=${endDate}`,
),
{
method: "GET",
headers: getHeaders(),
},
),
fetch(this.path(this.SubsPath), {
fetch(this.path(OpenaiPath.SubsPath), {
method: "GET",
headers: getHeaders(),
}),
@@ -226,4 +277,31 @@ export class ChatGPTApi implements LLMApi {
total: total.hard_limit_usd,
} as LLMUsage;
}
async models(): Promise<LLMModel[]> {
if (this.disableListModels) {
return DEFAULT_MODELS.slice();
}
const res = await fetch(this.path(OpenaiPath.ListModelPath), {
method: "GET",
headers: {
...getHeaders(),
},
});
const resJson = (await res.json()) as OpenAIListModelResponse;
const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-"));
console.log("[Models]", chatModels);
if (!chatModels) {
return [];
}
return chatModels.map((m) => ({
name: m.id,
available: true,
}));
}
}
export { OpenaiPath };

View File

@@ -1,28 +1,75 @@
import { useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import Locale from "./locales";
type Command = (param: string) => void;
interface Commands {
fill?: Command;
submit?: Command;
mask?: Command;
code?: Command;
settings?: Command;
}
export function useCommand(commands: Commands = {}) {
const [searchParams, setSearchParams] = useSearchParams();
if (commands === undefined) return;
useEffect(() => {
let shouldUpdate = false;
searchParams.forEach((param, name) => {
const commandName = name as keyof Commands;
if (typeof commands[commandName] === "function") {
commands[commandName]!(param);
searchParams.delete(name);
shouldUpdate = true;
}
});
let shouldUpdate = false;
searchParams.forEach((param, name) => {
const commandName = name as keyof Commands;
if (typeof commands[commandName] === "function") {
commands[commandName]!(param);
searchParams.delete(name);
shouldUpdate = true;
if (shouldUpdate) {
setSearchParams(searchParams);
}
});
if (shouldUpdate) {
setSearchParams(searchParams);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, commands]);
}
interface ChatCommands {
new?: Command;
newm?: Command;
next?: Command;
prev?: Command;
clear?: Command;
del?: Command;
}
export const ChatCommandPrefix = ":";
export function useChatCommand(commands: ChatCommands = {}) {
function extract(userInput: string) {
return (
userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput
) as keyof ChatCommands;
}
function search(userInput: string) {
const input = extract(userInput);
const desc = Locale.Chat.Commands;
return Object.keys(commands)
.filter((c) => c.startsWith(input))
.map((c) => ({
title: desc[c as keyof ChatCommands],
content: ChatCommandPrefix + c,
}));
}
function match(userInput: string) {
const command = extract(userInput);
const matched = typeof commands[command] === "function";
return {
matched,
invoke: () => matched && commands[command]!(userInput),
};
}
return { match, search };
}

View File

@@ -0,0 +1,36 @@
.auth-page {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
flex-direction: column;
.auth-logo {
transform: scale(1.4);
}
.auth-title {
font-size: 24px;
font-weight: bold;
line-height: 2;
}
.auth-tips {
font-size: 14px;
}
.auth-input {
margin: 3vh 0;
}
.auth-actions {
display: flex;
justify-content: center;
flex-direction: column;
button:not(:last-child) {
margin-bottom: 10px;
}
}
}

86
app/components/auth.tsx Normal file
View File

@@ -0,0 +1,86 @@
import styles from "./auth.module.scss";
import { IconButton } from "./button";
import { useNavigate } from "react-router-dom";
import { Path } from "../constant";
import { useAccessStore } from "../store";
import Locale from "../locales";
import BotIcon from "../icons/bot.svg";
import { useEffect } from "react";
import { getClientConfig } from "../config/client";
export function AuthPage() {
const navigate = useNavigate();
const accessStore = useAccessStore();
const goHome = () => navigate(Path.Home);
const goChat = () => navigate(Path.Chat);
const resetAccessCode = () => {
accessStore.update((access) => {
access.openaiApiKey = "";
access.accessCode = "";
});
}; // Reset access code to empty string
useEffect(() => {
if (getClientConfig()?.isApp) {
navigate(Path.Settings);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className={styles["auth-page"]}>
<div className={`no-dark ${styles["auth-logo"]}`}>
<BotIcon />
</div>
<div className={styles["auth-title"]}>{Locale.Auth.Title}</div>
<div className={styles["auth-tips"]}>{Locale.Auth.Tips}</div>
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Auth.Input}
value={accessStore.accessCode}
onChange={(e) => {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
);
}}
/>
{!accessStore.hideUserApiKey ? (
<>
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
value={accessStore.openaiApiKey}
onChange={(e) => {
accessStore.update(
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
</>
) : null}
<div className={styles["auth-actions"]}>
<IconButton
text={Locale.Auth.Confirm}
type="primary"
onClick={goChat}
/>
<IconButton
text={Locale.Auth.Later}
onClick={() => {
resetAccessCode();
goHome();
}}
/>
</div>
</div>
);
}

View File

@@ -27,6 +27,26 @@
fill: white !important;
}
}
&.danger {
color: rgba($color: red, $alpha: 0.8);
border-color: rgba($color: red, $alpha: 0.5);
background-color: rgba($color: red, $alpha: 0.05);
&:hover {
border-color: red;
background-color: rgba($color: red, $alpha: 0.1);
}
path {
fill: red !important;
}
}
&:hover,
&:focus {
border-color: var(--primary);
}
}
.shadow {
@@ -37,10 +57,6 @@
border: var(--border-in-light);
}
.icon-button:hover {
border-color: var(--primary);
}
.icon-button-icon {
width: 16px;
height: 16px;
@@ -56,9 +72,12 @@
}
.icon-button-text {
margin-left: 5px;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:not(:first-child) {
margin-left: 5px;
}
}

View File

@@ -2,16 +2,20 @@ import * as React from "react";
import styles from "./button.module.scss";
export type ButtonType = "primary" | "danger" | null;
export function IconButton(props: {
onClick?: () => void;
icon?: JSX.Element;
type?: "primary" | "danger";
type?: ButtonType;
text?: string;
bordered?: boolean;
shadow?: boolean;
className?: string;
title?: string;
disabled?: boolean;
tabIndex?: number;
autoFocus?: boolean;
}) {
return (
<button
@@ -25,6 +29,8 @@ export function IconButton(props: {
title={props.title}
disabled={props.disabled}
role="button"
tabIndex={props.tabIndex}
autoFocus={props.autoFocus}
>
{props.icon && (
<div

View File

@@ -17,6 +17,8 @@ import { Path } from "../constant";
import { MaskAvatar } from "./mask";
import { Mask } from "../store/mask";
import { useRef, useEffect } from "react";
import { showConfirm } from "./ui-lib";
import { useMobileScreen } from "../utils";
export function ChatItem(props: {
onClick?: () => void;
@@ -25,7 +27,7 @@ export function ChatItem(props: {
count: number;
time: string;
selected: boolean;
id: number;
id: string;
index: number;
narrow?: boolean;
mask: Mask;
@@ -72,16 +74,18 @@ export function ChatItem(props: {
<div className={styles["chat-item-count"]}>
{Locale.ChatItem.ChatItemCount(props.count)}
</div>
<div className={styles["chat-item-date"]}>
{new Date(props.time).toLocaleString()}
</div>
<div className={styles["chat-item-date"]}>{props.time}</div>
</div>
</>
)}
<div
className={styles["chat-item-delete"]}
onClickCapture={props.onDelete}
onClickCapture={(e) => {
props.onDelete?.();
e.preventDefault();
e.stopPropagation();
}}
>
<DeleteIcon />
</div>
@@ -102,6 +106,7 @@ export function ChatList(props: { narrow?: boolean }) {
);
const chatStore = useChatStore();
const navigate = useNavigate();
const isMobileScreen = useMobileScreen();
const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
@@ -141,8 +146,11 @@ export function ChatList(props: { narrow?: boolean }) {
navigate(Path.Chat);
selectSession(i);
}}
onDelete={() => {
if (!props.narrow || confirm(Locale.Home.DeleteChat)) {
onDelete={async () => {
if (
(!props.narrow && !isMobileScreen) ||
(await showConfirm(Locale.Home.DeleteChat))
) {
chatStore.deleteSession(i);
}
}}

View File

@@ -14,13 +14,43 @@
padding: 4px 10px;
animation: slide-in ease 0.3s;
box-shadow: var(--card-shadow);
transition: all ease 0.3s;
margin-bottom: 10px;
transition: width ease 0.3s;
align-items: center;
height: 16px;
width: var(--icon-width);
overflow: hidden;
&:not(:last-child) {
margin-right: 5px;
}
.text {
white-space: nowrap;
padding-left: 5px;
opacity: 0;
transform: translateX(-5px);
transition: all ease 0.3s;
pointer-events: none;
}
&:hover {
--delay: 0.5s;
width: var(--full-width);
transition-delay: var(--delay);
.text {
transition-delay: var(--delay);
opacity: 1;
transform: translate(0);
}
}
.text,
.icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
@@ -68,11 +98,41 @@
}
.context-prompt {
.context-prompt-insert {
display: flex;
justify-content: center;
padding: 4px;
opacity: 0.2;
transition: all ease 0.3s;
background-color: rgba(0, 0, 0, 0);
cursor: pointer;
border-radius: 4px;
margin-top: 4px;
margin-bottom: 4px;
&:hover {
opacity: 1;
background-color: rgba(0, 0, 0, 0.05);
}
}
.context-prompt-row {
display: flex;
justify-content: center;
width: 100%;
margin-bottom: 10px;
&:hover {
.context-drag {
opacity: 1;
}
}
.context-drag {
display: flex;
align-items: center;
opacity: 0.5;
transition: all ease 0.3s;
}
.context-role {
margin-right: 10px;
@@ -107,3 +167,352 @@
user-select: text;
}
}
.clear-context {
margin: 20px 0 0 0;
padding: 4px 0;
border-top: var(--border-in-light);
border-bottom: var(--border-in-light);
box-shadow: var(--card-shadow) inset;
display: flex;
justify-content: center;
align-items: center;
color: var(--black);
transition: all ease 0.3s;
cursor: pointer;
overflow: hidden;
position: relative;
font-size: 12px;
animation: slide-in ease 0.3s;
$linear: linear-gradient(
to right,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 0)
);
mask-image: $linear;
@mixin show {
transform: translateY(0);
position: relative;
transition: all ease 0.3s;
opacity: 1;
}
@mixin hide {
transform: translateY(-50%);
position: absolute;
transition: all ease 0.1s;
opacity: 0;
}
&-tips {
@include show;
opacity: 0.5;
}
&-revert-btn {
color: var(--primary);
@include hide;
}
&:hover {
opacity: 1;
border-color: var(--primary);
.clear-context-tips {
@include hide;
}
.clear-context-revert-btn {
@include show;
}
}
}
.chat {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.chat-body {
flex: 1;
overflow: auto;
overflow-x: hidden;
padding: 20px;
padding-bottom: 40px;
position: relative;
overscroll-behavior: none;
}
.chat-body-main-title {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
@media only screen and (max-width: 600px) {
.chat-body-title {
text-align: center;
}
}
.chat-message {
display: flex;
flex-direction: row;
&:last-child {
animation: slide-in ease 0.3s;
}
}
.chat-message-user {
display: flex;
flex-direction: row-reverse;
.chat-message-header {
flex-direction: row-reverse;
}
}
.chat-message-header {
margin-top: 20px;
display: flex;
align-items: center;
.chat-message-actions {
display: flex;
box-sizing: border-box;
font-size: 12px;
align-items: flex-end;
justify-content: space-between;
transition: all ease 0.3s;
transform: scale(0.9) translateY(5px);
margin: 0 10px;
opacity: 0;
pointer-events: none;
.chat-input-actions {
display: flex;
flex-wrap: nowrap;
}
}
}
.chat-message-container {
max-width: var(--message-max-width);
display: flex;
flex-direction: column;
align-items: flex-start;
&:hover {
.chat-message-edit {
opacity: 0.9;
}
.chat-message-actions {
opacity: 1;
pointer-events: all;
transform: scale(1) translateY(0);
}
}
}
.chat-message-user > .chat-message-container {
align-items: flex-end;
}
.chat-message-avatar {
position: relative;
.chat-message-edit {
position: absolute;
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all ease 0.3s;
button {
padding: 7px;
}
}
/* Specific styles for iOS devices */
@media screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2) {
@supports (-webkit-touch-callout: none) {
.chat-message-edit {
top: -8%;
}
}
}
}
.chat-message-status {
font-size: 12px;
color: #aaa;
line-height: 1.5;
margin-top: 5px;
}
.chat-message-item {
box-sizing: border-box;
max-width: 100%;
margin-top: 10px;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
font-size: 14px;
user-select: text;
word-break: break-word;
border: var(--border-in-light);
position: relative;
transition: all ease 0.3s;
}
.chat-message-action-date {
font-size: 12px;
opacity: 0.2;
white-space: nowrap;
transition: all ease 0.6s;
color: var(--black);
text-align: right;
width: 100%;
box-sizing: border-box;
padding-right: 10px;
pointer-events: none;
z-index: 1;
}
.chat-message-user > .chat-message-container > .chat-message-item {
background-color: var(--second);
&:hover {
min-width: 0;
}
}
.chat-input-panel {
position: relative;
width: 100%;
padding: 20px;
padding-top: 10px;
box-sizing: border-box;
flex-direction: column;
border-top: var(--border-in-light);
box-shadow: var(--card-shadow);
.chat-input-actions {
.chat-input-action {
margin-bottom: 10px;
}
}
}
@mixin single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prompt-hints {
min-height: 20px;
width: 100%;
max-height: 50vh;
overflow: auto;
display: flex;
flex-direction: column-reverse;
background-color: var(--white);
border: var(--border-in-light);
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--shadow);
.prompt-hint {
color: var(--black);
padding: 6px 10px;
animation: slide-in ease 0.3s;
cursor: pointer;
transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;
&:not(:last-child) {
margin-top: 0;
}
.hint-title {
font-size: 12px;
font-weight: bolder;
@include single-line();
}
.hint-content {
font-size: 12px;
@include single-line();
}
&-selected,
&:hover {
border-color: var(--primary);
}
}
}
.chat-input-panel-inner {
display: flex;
flex: 1;
}
.chat-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px 90px 10px 14px;
resize: none;
outline: none;
box-sizing: border-box;
min-height: 68px;
}
.chat-input:focus {
border: 1px solid var(--primary);
}
.chat-input-send {
background-color: var(--primary);
color: white;
position: absolute;
right: 30px;
bottom: 32px;
}
@media only screen and (max-width: 600px) {
.chat-input {
font-size: 16px;
}
.chat-input-send {
bottom: 30px;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,8 @@ import GithubIcon from "../icons/github.svg";
import ResetIcon from "../icons/reload.svg";
import { ISSUE_URL } from "../constant";
import Locale from "../locales";
import { downloadAs } from "../utils";
import { showConfirm } from "./ui-lib";
import { useSyncStore } from "../store/sync";
interface IErrorBoundaryState {
hasError: boolean;
@@ -25,10 +26,7 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
clearAndSaveData() {
try {
downloadAs(
JSON.stringify(localStorage),
"chatgpt-next-web-snapshot.json",
);
useSyncStore.getState().export();
} finally {
localStorage.clear();
location.reload();
@@ -57,10 +55,11 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
<IconButton
icon={<ResetIcon />}
text="Clear All Data"
onClick={() =>
confirm(Locale.Settings.Actions.ConfirmClearAll) &&
this.clearAndSaveData()
}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
this.clearAndSaveData();
}
}}
bordered
/>
</div>

View File

@@ -0,0 +1,217 @@
.message-exporter {
&-body {
margin-top: 20px;
}
}
.export-content {
white-space: break-spaces;
padding: 10px !important;
}
.steps {
background-color: var(--gray);
border-radius: 10px;
overflow: hidden;
padding: 5px;
position: relative;
box-shadow: var(--card-shadow) inset;
.steps-progress {
$padding: 5px;
height: calc(100% - 2 * $padding);
width: calc(100% - 2 * $padding);
position: absolute;
top: $padding;
left: $padding;
&-inner {
box-sizing: border-box;
box-shadow: var(--card-shadow);
border: var(--border-in-light);
content: "";
display: inline-block;
width: 0%;
height: 100%;
background-color: var(--white);
transition: all ease 0.3s;
border-radius: 8px;
}
}
.steps-inner {
display: flex;
transform: scale(1);
.step {
flex-grow: 1;
padding: 5px 10px;
font-size: 14px;
color: var(--black);
opacity: 0.5;
transition: all ease 0.3s;
display: flex;
align-items: center;
justify-content: center;
$radius: 8px;
&-finished {
opacity: 0.9;
}
&:hover {
opacity: 0.8;
}
&-current {
color: var(--primary);
}
.step-index {
background-color: var(--gray);
border: var(--border-in-light);
border-radius: 6px;
display: inline-block;
padding: 0px 5px;
font-size: 12px;
margin-right: 8px;
opacity: 0.8;
}
.step-name {
font-size: 12px;
}
}
}
}
.preview-actions {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
button {
flex-grow: 1;
&:not(:last-child) {
margin-right: 10px;
}
}
}
.image-previewer {
.preview-body {
border-radius: 10px;
padding: 20px;
box-shadow: var(--card-shadow) inset;
background-color: var(--gray);
.chat-info {
background-color: var(--second);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: flex-end;
position: relative;
overflow: hidden;
@media screen and (max-width: 600px) {
flex-direction: column;
align-items: flex-start;
.icons {
margin-bottom: 20px;
}
}
.logo {
position: absolute;
top: 0px;
left: 0px;
height: 50%;
transform: scale(1.5);
}
.main-title {
font-size: 20px;
font-weight: bolder;
}
.sub-title {
font-size: 12px;
}
.icons {
margin-top: 10px;
display: flex;
align-items: center;
.icon-space {
font-size: 12px;
margin: 0 10px;
font-weight: bolder;
color: var(--primary);
}
}
.chat-info-item {
font-size: 12px;
color: var(--primary);
padding: 2px 15px;
border-radius: 10px;
background-color: var(--white);
box-shadow: var(--card-shadow);
&:not(:last-child) {
margin-bottom: 5px;
}
}
}
.message {
margin-bottom: 20px;
display: flex;
.avatar {
margin-right: 10px;
}
.body {
border-radius: 10px;
padding: 8px 10px;
max-width: calc(100% - 104px);
box-shadow: var(--card-shadow);
border: var(--border-in-light);
*:not(li) {
overflow: hidden;
}
}
&-assistant {
.body {
background-color: var(--white);
}
}
&-user {
flex-direction: row-reverse;
.avatar {
margin-right: 0;
}
.body {
background-color: var(--second);
margin-right: 10px;
}
}
}
}
.default-theme {
}
}

648
app/components/exporter.tsx Normal file
View File

@@ -0,0 +1,648 @@
/* eslint-disable @next/next/no-img-element */
import { ChatMessage, useAppConfig, useChatStore } from "../store";
import Locale from "../locales";
import styles from "./exporter.module.scss";
import {
List,
ListItem,
Modal,
Select,
showImageModal,
showModal,
showToast,
} from "./ui-lib";
import { IconButton } from "./button";
import { copyToClipboard, downloadAs, useMobileScreen } from "../utils";
import CopyIcon from "../icons/copy.svg";
import LoadingIcon from "../icons/three-dots.svg";
import ChatGptIcon from "../icons/chatgpt.png";
import ShareIcon from "../icons/share.svg";
import BotIcon from "../icons/bot.png";
import DownloadIcon from "../icons/download.svg";
import { useEffect, useMemo, useRef, useState } from "react";
import { MessageSelector, useMessageSelector } from "./message-selector";
import { Avatar } from "./emoji";
import dynamic from "next/dynamic";
import NextImage from "next/image";
import { toBlob, toJpeg, toPng } from "html-to-image";
import { DEFAULT_MASK_AVATAR } from "../store/mask";
import { api } from "../client/api";
import { prettyObject } from "../utils/format";
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
import { getClientConfig } from "../config/client";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />,
});
export function ExportMessageModal(props: { onClose: () => void }) {
return (
<div className="modal-mask">
<Modal title={Locale.Export.Title} onClose={props.onClose}>
<div style={{ minHeight: "40vh" }}>
<MessageExporter />
</div>
</Modal>
</div>
);
}
function useSteps(
steps: Array<{
name: string;
value: string;
}>,
) {
const stepCount = steps.length;
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const nextStep = () =>
setCurrentStepIndex((currentStepIndex + 1) % stepCount);
const prevStep = () =>
setCurrentStepIndex((currentStepIndex - 1 + stepCount) % stepCount);
return {
currentStepIndex,
setCurrentStepIndex,
nextStep,
prevStep,
currentStep: steps[currentStepIndex],
};
}
function Steps<
T extends {
name: string;
value: string;
}[],
>(props: { steps: T; onStepChange?: (index: number) => void; index: number }) {
const steps = props.steps;
const stepCount = steps.length;
return (
<div className={styles["steps"]}>
<div className={styles["steps-progress"]}>
<div
className={styles["steps-progress-inner"]}
style={{
width: `${((props.index + 1) / stepCount) * 100}%`,
}}
></div>
</div>
<div className={styles["steps-inner"]}>
{steps.map((step, i) => {
return (
<div
key={i}
className={`${styles["step"]} ${
styles[i <= props.index ? "step-finished" : ""]
} ${i === props.index && styles["step-current"]} clickable`}
onClick={() => {
props.onStepChange?.(i);
}}
role="button"
>
<span className={styles["step-index"]}>{i + 1}</span>
<span className={styles["step-name"]}>{step.name}</span>
</div>
);
})}
</div>
</div>
);
}
export function MessageExporter() {
const steps = [
{
name: Locale.Export.Steps.Select,
value: "select",
},
{
name: Locale.Export.Steps.Preview,
value: "preview",
},
];
const { currentStep, setCurrentStepIndex, currentStepIndex } =
useSteps(steps);
const formats = ["text", "image", "json"] as const;
type ExportFormat = (typeof formats)[number];
const [exportConfig, setExportConfig] = useState({
format: "image" as ExportFormat,
includeContext: true,
});
function updateExportConfig(updater: (config: typeof exportConfig) => void) {
const config = { ...exportConfig };
updater(config);
setExportConfig(config);
}
const chatStore = useChatStore();
const session = chatStore.currentSession();
const { selection, updateSelection } = useMessageSelector();
const selectedMessages = useMemo(() => {
const ret: ChatMessage[] = [];
if (exportConfig.includeContext) {
ret.push(...session.mask.context);
}
ret.push(...session.messages.filter((m, i) => selection.has(m.id)));
return ret;
}, [
exportConfig.includeContext,
session.messages,
session.mask.context,
selection,
]);
function preview() {
if (exportConfig.format === "text") {
return (
<MarkdownPreviewer messages={selectedMessages} topic={session.topic} />
);
} else if (exportConfig.format === "json") {
return (
<JsonPreviewer messages={selectedMessages} topic={session.topic} />
);
} else {
return (
<ImagePreviewer messages={selectedMessages} topic={session.topic} />
);
}
}
return (
<>
<Steps
steps={steps}
index={currentStepIndex}
onStepChange={setCurrentStepIndex}
/>
<div
className={styles["message-exporter-body"]}
style={currentStep.value !== "select" ? { display: "none" } : {}}
>
<List>
<ListItem
title={Locale.Export.Format.Title}
subTitle={Locale.Export.Format.SubTitle}
>
<Select
value={exportConfig.format}
onChange={(e) =>
updateExportConfig(
(config) =>
(config.format = e.currentTarget.value as ExportFormat),
)
}
>
{formats.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</Select>
</ListItem>
<ListItem
title={Locale.Export.IncludeContext.Title}
subTitle={Locale.Export.IncludeContext.SubTitle}
>
<input
type="checkbox"
checked={exportConfig.includeContext}
onChange={(e) => {
updateExportConfig(
(config) => (config.includeContext = e.currentTarget.checked),
);
}}
></input>
</ListItem>
</List>
<MessageSelector
selection={selection}
updateSelection={updateSelection}
defaultSelectAll
/>
</div>
{currentStep.value === "preview" && (
<div className={styles["message-exporter-body"]}>{preview()}</div>
)}
</>
);
}
export function RenderExport(props: {
messages: ChatMessage[];
onRender: (messages: ChatMessage[]) => void;
}) {
const domRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!domRef.current) return;
const dom = domRef.current;
const messages = Array.from(
dom.getElementsByClassName(EXPORT_MESSAGE_CLASS_NAME),
);
if (messages.length !== props.messages.length) {
return;
}
const renderMsgs = messages.map((v, i) => {
const [role, _] = v.id.split(":");
return {
id: i.toString(),
role: role as any,
content: role === "user" ? v.textContent ?? "" : v.innerHTML,
date: "",
};
});
props.onRender(renderMsgs);
});
return (
<div ref={domRef}>
{props.messages.map((m, i) => (
<div
key={i}
id={`${m.role}:${i}`}
className={EXPORT_MESSAGE_CLASS_NAME}
>
<Markdown content={m.content} defaultShow />
</div>
))}
</div>
);
}
export function PreviewActions(props: {
download: () => void;
copy: () => void;
showCopy?: boolean;
messages?: ChatMessage[];
}) {
const [loading, setLoading] = useState(false);
const [shouldExport, setShouldExport] = useState(false);
const onRenderMsgs = (msgs: ChatMessage[]) => {
setShouldExport(false);
api
.share(msgs)
.then((res) => {
if (!res) return;
showModal({
title: Locale.Export.Share,
children: [
<input
type="text"
value={res}
key="input"
style={{
width: "100%",
maxWidth: "unset",
}}
readOnly
onClick={(e) => e.currentTarget.select()}
></input>,
],
actions: [
<IconButton
icon={<CopyIcon />}
text={Locale.Chat.Actions.Copy}
key="copy"
onClick={() => copyToClipboard(res)}
/>,
],
});
setTimeout(() => {
window.open(res, "_blank");
}, 800);
})
.catch((e) => {
console.error("[Share]", e);
showToast(prettyObject(e));
})
.finally(() => setLoading(false));
};
const share = async () => {
if (props.messages?.length) {
setLoading(true);
setShouldExport(true);
}
};
return (
<>
<div className={styles["preview-actions"]}>
{props.showCopy && (
<IconButton
text={Locale.Export.Copy}
bordered
shadow
icon={<CopyIcon />}
onClick={props.copy}
></IconButton>
)}
<IconButton
text={Locale.Export.Download}
bordered
shadow
icon={<DownloadIcon />}
onClick={props.download}
></IconButton>
<IconButton
text={Locale.Export.Share}
bordered
shadow
icon={loading ? <LoadingIcon /> : <ShareIcon />}
onClick={share}
></IconButton>
</div>
<div
style={{
position: "fixed",
right: "200vw",
pointerEvents: "none",
}}
>
{shouldExport && (
<RenderExport
messages={props.messages ?? []}
onRender={onRenderMsgs}
/>
)}
</div>
</>
);
}
function ExportAvatar(props: { avatar: string }) {
if (props.avatar === DEFAULT_MASK_AVATAR) {
return (
<img
src={BotIcon.src}
width={30}
height={30}
alt="bot"
className="user-avatar"
/>
);
}
return <Avatar avatar={props.avatar} />;
}
export function ImagePreviewer(props: {
messages: ChatMessage[];
topic: string;
}) {
const chatStore = useChatStore();
const session = chatStore.currentSession();
const mask = session.mask;
const config = useAppConfig();
const previewRef = useRef<HTMLDivElement>(null);
const copy = () => {
showToast(Locale.Export.Image.Toast);
const dom = previewRef.current;
if (!dom) return;
toBlob(dom).then((blob) => {
if (!blob) return;
try {
navigator.clipboard
.write([
new ClipboardItem({
"image/png": blob,
}),
])
.then(() => {
showToast(Locale.Copy.Success);
refreshPreview();
});
} catch (e) {
console.error("[Copy Image] ", e);
showToast(Locale.Copy.Failed);
}
});
};
const isMobile = useMobileScreen();
const download = async () => {
showToast(Locale.Export.Image.Toast);
const dom = previewRef.current;
if (!dom) return;
const isApp = getClientConfig()?.isApp;
try {
const blob = await toPng(dom);
if (!blob) return;
if (isMobile || (isApp && window.__TAURI__)) {
if (isApp && window.__TAURI__) {
const result = await window.__TAURI__.dialog.save({
defaultPath: `${props.topic}.png`,
filters: [
{
name: "PNG Files",
extensions: ["png"],
},
{
name: "All Files",
extensions: ["*"],
},
],
});
if (result !== null) {
const response = await fetch(blob);
const buffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(buffer);
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
showToast(Locale.Download.Success);
} else {
showToast(Locale.Download.Failed);
}
} else {
showImageModal(blob);
}
} else {
const link = document.createElement("a");
link.download = `${props.topic}.png`;
link.href = blob;
link.click();
refreshPreview();
}
} catch (error) {
showToast(Locale.Download.Failed);
}
};
const refreshPreview = () => {
const dom = previewRef.current;
if (dom) {
dom.innerHTML = dom.innerHTML; // Refresh the content of the preview by resetting its HTML for fix a bug glitching
}
};
return (
<div className={styles["image-previewer"]}>
<PreviewActions
copy={copy}
download={download}
showCopy={!isMobile}
messages={props.messages}
/>
<div
className={`${styles["preview-body"]} ${styles["default-theme"]}`}
ref={previewRef}
>
<div className={styles["chat-info"]}>
<div className={styles["logo"] + " no-dark"}>
<NextImage
src={ChatGptIcon.src}
alt="logo"
width={50}
height={50}
/>
</div>
<div>
<div className={styles["main-title"]}>ChatGPT Next Web</div>
<div className={styles["sub-title"]}>
github.com/Yidadaa/ChatGPT-Next-Web
</div>
<div className={styles["icons"]}>
<ExportAvatar avatar={config.avatar} />
<span className={styles["icon-space"]}>&</span>
<ExportAvatar avatar={mask.avatar} />
</div>
</div>
<div>
<div className={styles["chat-info-item"]}>
{Locale.Exporter.Model}: {mask.modelConfig.model}
</div>
<div className={styles["chat-info-item"]}>
{Locale.Exporter.Messages}: {props.messages.length}
</div>
<div className={styles["chat-info-item"]}>
{Locale.Exporter.Topic}: {session.topic}
</div>
<div className={styles["chat-info-item"]}>
{Locale.Exporter.Time}:{" "}
{new Date(
props.messages.at(-1)?.date ?? Date.now(),
).toLocaleString()}
</div>
</div>
</div>
{props.messages.map((m, i) => {
return (
<div
className={styles["message"] + " " + styles["message-" + m.role]}
key={i}
>
<div className={styles["avatar"]}>
<ExportAvatar
avatar={m.role === "user" ? config.avatar : mask.avatar}
/>
</div>
<div className={styles["body"]}>
<Markdown
content={m.content}
fontSize={config.fontSize}
defaultShow
/>
</div>
</div>
);
})}
</div>
</div>
);
}
export function MarkdownPreviewer(props: {
messages: ChatMessage[];
topic: string;
}) {
const mdText =
`# ${props.topic}\n\n` +
props.messages
.map((m) => {
return m.role === "user"
? `## ${Locale.Export.MessageFromYou}:\n${m.content}`
: `## ${Locale.Export.MessageFromChatGPT}:\n${m.content.trim()}`;
})
.join("\n\n");
const copy = () => {
copyToClipboard(mdText);
};
const download = () => {
downloadAs(mdText, `${props.topic}.md`);
};
return (
<>
<PreviewActions
copy={copy}
download={download}
showCopy={true}
messages={props.messages}
/>
<div className="markdown-body">
<pre className={styles["export-content"]}>{mdText}</pre>
</div>
</>
);
}
// modified by BackTrackZ now it's looks better
export function JsonPreviewer(props: {
messages: ChatMessage[];
topic: string;
}) {
const msgs = {
messages: [
{
role: "system",
content: `${Locale.FineTuned.Sysmessage} ${props.topic}`,
},
...props.messages.map((m) => ({
role: m.role,
content: m.content,
})),
],
};
const mdText = "```json\n" + JSON.stringify(msgs, null, 2) + "\n```";
const minifiedJson = JSON.stringify(msgs);
const copy = () => {
copyToClipboard(minifiedJson);
};
const download = () => {
downloadAs(JSON.stringify(msgs), `${props.topic}.json`);
};
return (
<>
<PreviewActions
copy={copy}
download={download}
showCopy={false}
messages={props.messages}
/>
<div className="markdown-body" onClick={copy}>
<Markdown content={mdText} />
</div>
</>
);
}

View File

@@ -6,7 +6,7 @@
color: var(--black);
background-color: var(--white);
min-width: 600px;
min-height: 480px;
min-height: 370px;
max-width: 1200px;
display: flex;
@@ -61,24 +61,36 @@
}
}
}
&:hover,
&:active {
.sidebar-drag {
background-color: rgba($color: #000000, $alpha: 0.01);
svg {
opacity: 0.2;
}
}
}
}
.sidebar-drag {
$width: 10px;
$width: 14px;
position: absolute;
top: 0;
right: 0;
height: 100%;
width: $width;
background-color: var(--black);
background-color: rgba($color: #000000, $alpha: 0);
cursor: ew-resize;
opacity: 0;
transition: all ease 0.3s;
display: flex;
align-items: center;
&:hover,
&:active {
opacity: 0.2;
svg {
opacity: 0;
margin-left: -2px;
}
}
@@ -141,7 +153,7 @@
.sidebar-sub-title {
font-size: 12px;
font-weight: 400px;
font-weight: 400;
animation: slide-in ease 0.3s;
}
@@ -162,6 +174,7 @@
user-select: none;
border: 2px solid transparent;
position: relative;
content-visibility: auto;
}
.chat-item:hover {
@@ -176,7 +189,7 @@
font-size: 14px;
font-weight: bolder;
display: block;
width: 200px;
width: calc(100% - 15px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -185,7 +198,7 @@
.chat-item-delete {
position: absolute;
top: 10px;
top: 0;
right: 0;
transition: all ease 0.3s;
opacity: 0;
@@ -194,7 +207,7 @@
.chat-item:hover > .chat-item-delete {
opacity: 0.5;
transform: translateX(-10px);
transform: translateX(-4px);
}
.chat-item:hover > .chat-item-delete:hover {
@@ -283,15 +296,6 @@
}
}
.chat-item-delete {
top: 15px;
}
.chat-item:hover > .chat-item-delete {
opacity: 0.5;
right: 5px;
}
.sidebar-tail {
flex-direction: column-reverse;
align-items: center;
@@ -322,247 +326,6 @@
margin-right: 15px;
}
.chat {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.chat-body {
flex: 1;
overflow: auto;
padding: 20px;
padding-bottom: 40px;
position: relative;
overscroll-behavior: none;
}
.chat-body-title {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.chat-message {
display: flex;
flex-direction: row;
&:last-child {
animation: slide-in ease 0.3s;
}
}
.chat-message-user {
display: flex;
flex-direction: row-reverse;
}
.chat-message-container {
max-width: var(--message-max-width);
display: flex;
flex-direction: column;
align-items: flex-start;
&:hover {
.chat-message-top-actions {
opacity: 1;
right: 10px;
pointer-events: all;
}
}
}
.chat-message-user > .chat-message-container {
align-items: flex-end;
}
.chat-message-avatar {
margin-top: 20px;
}
.chat-message-status {
font-size: 12px;
color: #aaa;
line-height: 1.5;
margin-top: 5px;
}
.chat-message-item {
box-sizing: border-box;
max-width: 100%;
margin-top: 10px;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
font-size: 14px;
user-select: text;
word-break: break-word;
border: var(--border-in-light);
position: relative;
}
.chat-message-top-actions {
font-size: 12px;
position: absolute;
right: 20px;
top: -26px;
left: 100px;
transition: all ease 0.3s;
opacity: 0;
pointer-events: none;
display: flex;
flex-direction: row-reverse;
.chat-message-top-action {
opacity: 0.5;
color: var(--black);
white-space: nowrap;
cursor: pointer;
&:hover {
opacity: 1;
}
&:not(:first-child) {
margin-right: 10px;
}
}
}
.chat-message-user > .chat-message-container > .chat-message-item {
background-color: var(--second);
}
.chat-message-actions {
display: flex;
flex-direction: row-reverse;
width: 100%;
padding-top: 5px;
box-sizing: border-box;
font-size: 12px;
}
.chat-message-action-date {
color: #aaa;
}
.chat-input-panel {
position: relative;
width: 100%;
padding: 20px;
padding-top: 10px;
box-sizing: border-box;
flex-direction: column;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-top: var(--border-in-light);
box-shadow: var(--card-shadow);
}
@mixin single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prompt-hints {
min-height: 20px;
width: 100%;
max-height: 50vh;
overflow: auto;
display: flex;
flex-direction: column-reverse;
background-color: var(--white);
border: var(--border-in-light);
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--shadow);
.prompt-hint {
color: var(--black);
padding: 6px 10px;
animation: slide-in ease 0.3s;
cursor: pointer;
transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;
&:not(:last-child) {
margin-top: 0;
}
.hint-title {
font-size: 12px;
font-weight: bolder;
@include single-line();
}
.hint-content {
font-size: 12px;
@include single-line();
}
&-selected,
&:hover {
border-color: var(--primary);
}
}
}
.chat-input-panel-inner {
display: flex;
flex: 1;
}
.chat-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px 90px 10px 14px;
resize: none;
outline: none;
}
.chat-input:focus {
border: 1px solid var(--primary);
}
.chat-input-send {
background-color: var(--primary);
color: white;
position: absolute;
right: 30px;
bottom: 32px;
}
@media only screen and (max-width: 600px) {
.chat-input {
font-size: 16px;
}
.chat-input-send {
bottom: 30px;
}
}
.export-content {
white-space: break-spaces;
padding: 10px !important;
}
.loading-content {
display: flex;
flex-direction: column;
@@ -571,3 +334,7 @@
height: 100%;
width: 100%;
}
.rtl-screen {
direction: rtl;
}

View File

@@ -15,6 +15,8 @@ import dynamic from "next/dynamic";
import { Path, SlotID } from "../constant";
import { ErrorBoundary } from "./error";
import { getISOLang, getLang } from "../locales";
import {
HashRouter as Router,
Routes,
@@ -23,6 +25,10 @@ import {
} from "react-router-dom";
import { SideBar } from "./sidebar";
import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client";
import { api } from "../client/api";
import { useAccessStore } from "../store";
export function Loading(props: { noLogo?: boolean }) {
return (
@@ -80,6 +86,17 @@ export function useSwitchTheme() {
}, [config.theme]);
}
function useHtmlLang() {
useEffect(() => {
const lang = getISOLang();
const htmlLang = document.documentElement.lang;
if (lang !== htmlLang) {
document.documentElement.lang = lang;
}
}, []);
}
const useHasHydrated = () => {
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
@@ -92,9 +109,16 @@ const useHasHydrated = () => {
const loadAsyncGoogleFont = () => {
const linkEl = document.createElement("link");
const proxyFontUrl = "/google-fonts";
const remoteFontUrl = "https://fonts.googleapis.com";
const googleFontUrl =
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
linkEl.rel = "stylesheet";
linkEl.href =
"/google-fonts/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
googleFontUrl +
"/css2?family=" +
encodeURIComponent("Noto Sans:wght@300;400;700;900") +
"&display=swap";
document.head.appendChild(linkEl);
};
@@ -102,7 +126,9 @@ function Screen() {
const config = useAppConfig();
const location = useLocation();
const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth;
const isMobileScreen = useMobileScreen();
const shouldTightBorder = getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
useEffect(() => {
loadAsyncGoogleFont();
@@ -112,30 +138,55 @@ function Screen() {
<div
className={
styles.container +
` ${
config.tightBorder && !isMobileScreen
? styles["tight-container"]
: styles.container
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
getLang() === "ar" ? styles["rtl-screen"] : ""
}`
}
>
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
{isAuth ? (
<>
<AuthPage />
</>
) : (
<>
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
<div className={styles["window-content"]} id={SlotID.AppBody}>
<Routes>
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</div>
<div className={styles["window-content"]} id={SlotID.AppBody}>
<Routes>
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
</div>
</>
)}
</div>
);
}
export function useLoadData() {
const config = useAppConfig();
useEffect(() => {
(async () => {
const models = await api.llm.models();
config.mergeModels(models);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}
export function Home() {
useSwitchTheme();
useLoadData();
useHtmlLang();
useEffect(() => {
console.log("[Config] got config from build time", getClientConfig());
useAccessStore.getState().fetch();
}, []);
if (!useHasHydrated()) {
return <Loading />;

View File

@@ -1,7 +1,13 @@
.input-range {
border: var(--border-in-light);
border-radius: 10px;
padding: 5px 15px 5px 10px;
padding: 5px 10px 5px 10px;
font-size: 12px;
display: flex;
justify-content: space-between;
max-width: 40%;
input[type="range"] {
max-width: calc(100% - 34px);
}
}

View File

@@ -11,18 +11,22 @@ import mermaid from "mermaid";
import LoadingIcon from "../icons/three-dots.svg";
import React from "react";
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
import { showImageModal } from "./ui-lib";
export function Mermaid(props: { code: string; onError: () => void }) {
export function Mermaid(props: { code: string }) {
const ref = useRef<HTMLDivElement>(null);
const [hasError, setHasError] = useState(false);
useEffect(() => {
if (props.code && ref.current) {
mermaid
.run({
nodes: [ref.current],
suppressErrors: true,
})
.catch((e) => {
props.onError();
setHasError(true);
console.error("[Mermaid] ", e.message);
});
}
@@ -34,17 +38,20 @@ export function Mermaid(props: { code: string; onError: () => void }) {
if (!svg) return;
const text = new XMLSerializer().serializeToString(svg);
const blob = new Blob([text], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const win = window.open(url);
if (win) {
win.onload = () => URL.revokeObjectURL(url);
}
showImageModal(URL.createObjectURL(blob));
}
if (hasError) {
return null;
}
return (
<div
className="no-dark"
style={{ cursor: "pointer", overflow: "auto" }}
className="no-dark mermaid"
style={{
cursor: "pointer",
overflow: "auto",
}}
ref={ref}
onClick={() => viewSvgInNewWindow()}
>
@@ -55,33 +62,40 @@ export function Mermaid(props: { code: string; onError: () => void }) {
export function PreCode(props: { children: any }) {
const ref = useRef<HTMLPreElement>(null);
const refText = ref.current?.innerText;
const [mermaidCode, setMermaidCode] = useState("");
useEffect(() => {
const renderMermaid = useDebouncedCallback(() => {
if (!ref.current) return;
const mermaidDom = ref.current.querySelector("code.language-mermaid");
if (mermaidDom) {
setMermaidCode((mermaidDom as HTMLElement).innerText);
}
}, [props.children]);
}, 600);
if (mermaidCode) {
return <Mermaid code={mermaidCode} onError={() => setMermaidCode("")} />;
}
useEffect(() => {
setTimeout(renderMermaid, 1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refText]);
return (
<pre ref={ref}>
<span
className="copy-code-button"
onClick={() => {
if (ref.current) {
const code = ref.current.innerText;
copyToClipboard(code);
}
}}
></span>
{props.children}
</pre>
<>
{mermaidCode.length > 0 && (
<Mermaid code={mermaidCode} key={mermaidCode} />
)}
<pre ref={ref}>
<span
className="copy-code-button"
onClick={() => {
if (ref.current) {
const code = ref.current.innerText;
copyToClipboard(code);
}
}}
></span>
{props.children}
</pre>
</>
);
}
@@ -101,6 +115,7 @@ function _MarkDownContent(props: { content: string }) {
]}
components={{
pre: PreCode,
p: (pProps) => <p {...pProps} dir="auto" />,
a: (aProps) => {
const href = aProps.href || "";
const isInternal = /^\/#/i.test(href);
@@ -121,60 +136,28 @@ export function Markdown(
content: string;
loading?: boolean;
fontSize?: number;
parentRef: RefObject<HTMLDivElement>;
parentRef?: RefObject<HTMLDivElement>;
defaultShow?: boolean;
} & React.DOMAttributes<HTMLDivElement>,
) {
const mdRef = useRef<HTMLDivElement>(null);
const renderedHeight = useRef(0);
const inView = useRef(!!props.defaultShow);
const parent = props.parentRef.current;
const md = mdRef.current;
const checkInView = () => {
if (parent && md) {
const parentBounds = parent.getBoundingClientRect();
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
const mdBounds = md.getBoundingClientRect();
const parentTop = parentBounds.top - twoScreenHeight;
const parentBottom = parentBounds.bottom + twoScreenHeight;
const isOverlap =
Math.max(parentTop, mdBounds.top) <=
Math.min(parentBottom, mdBounds.bottom);
inView.current = isOverlap;
}
if (inView.current && md) {
renderedHeight.current = Math.max(
renderedHeight.current,
md.getBoundingClientRect().height,
);
}
};
setTimeout(() => checkInView(), 1);
return (
<div
className="markdown-body"
style={{
fontSize: `${props.fontSize ?? 14}px`,
height:
!inView.current && renderedHeight.current > 0
? renderedHeight.current
: "auto",
}}
ref={mdRef}
onContextMenu={props.onContextMenu}
onDoubleClickCapture={props.onDoubleClickCapture}
dir="auto"
>
{inView.current &&
(props.loading ? (
<LoadingIcon />
) : (
<MarkdownContent content={props.content} />
))}
{props.loading ? (
<LoadingIcon />
) : (
<MarkdownContent content={props.content} />
)}
</div>
);
}

View File

@@ -11,22 +11,52 @@ import CloseIcon from "../icons/close.svg";
import DeleteIcon from "../icons/delete.svg";
import EyeIcon from "../icons/eye.svg";
import CopyIcon from "../icons/copy.svg";
import DragIcon from "../icons/drag.svg";
import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
import { ChatMessage, ModelConfig, useChatStore } from "../store";
import {
ChatMessage,
createMessage,
ModelConfig,
useAppConfig,
useChatStore,
} from "../store";
import { ROLES } from "../client/api";
import { Input, List, ListItem, Modal, Popover, Select } from "./ui-lib";
import {
Input,
List,
ListItem,
Modal,
Popover,
Select,
showConfirm,
} from "./ui-lib";
import { Avatar, AvatarPicker } from "./emoji";
import Locale, { AllLangs, Lang } from "../locales";
import Locale, { AllLangs, ALL_LANG_OPTIONS, Lang } from "../locales";
import { useNavigate } from "react-router-dom";
import chatStyle from "./chat.module.scss";
import { useState } from "react";
import { downloadAs, readFromFile } from "../utils";
import { useEffect, useState } from "react";
import { copyToClipboard, downloadAs, readFromFile } from "../utils";
import { Updater } from "../typing";
import { ModelConfigList } from "./model-config";
import { FileName, Path } from "../constant";
import { BUILTIN_MASK_STORE } from "../masks";
import { nanoid } from "nanoid";
import {
DragDropContext,
Droppable,
Draggable,
OnDragEndResponder,
} from "@hello-pangea/dnd";
// drag and drop helper function
function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
const result = [...list];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
}
export function MaskAvatar(props: { mask: Mask }) {
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
@@ -41,6 +71,7 @@ export function MaskConfig(props: {
updateMask: Updater<Mask>;
extraListItems?: JSX.Element;
readonly?: boolean;
shouldSyncFromGlobal?: boolean;
}) {
const [showPicker, setShowPicker] = useState(false);
@@ -49,9 +80,20 @@ export function MaskConfig(props: {
const config = { ...props.mask.modelConfig };
updater(config);
props.updateMask((mask) => (mask.modelConfig = config));
props.updateMask((mask) => {
mask.modelConfig = config;
// if user changed current session mask, it will disable auto sync
mask.syncGlobalConfig = false;
});
};
const copyMaskLink = () => {
const maskLink = `${location.protocol}//${location.host}/#${Path.NewChat}?mask=${props.mask.id}`;
copyToClipboard(maskLink);
};
const globalConfig = useAppConfig();
return (
<>
<ContextPrompts
@@ -90,10 +132,67 @@ export function MaskConfig(props: {
type="text"
value={props.mask.name}
onInput={(e) =>
props.updateMask((mask) => (mask.name = e.currentTarget.value))
props.updateMask((mask) => {
mask.name = e.currentTarget.value;
})
}
></input>
</ListItem>
<ListItem
title={Locale.Mask.Config.HideContext.Title}
subTitle={Locale.Mask.Config.HideContext.SubTitle}
>
<input
type="checkbox"
checked={props.mask.hideContext}
onChange={(e) => {
props.updateMask((mask) => {
mask.hideContext = e.currentTarget.checked;
});
}}
></input>
</ListItem>
{!props.shouldSyncFromGlobal ? (
<ListItem
title={Locale.Mask.Config.Share.Title}
subTitle={Locale.Mask.Config.Share.SubTitle}
>
<IconButton
icon={<CopyIcon />}
text={Locale.Mask.Config.Share.Action}
onClick={copyMaskLink}
/>
</ListItem>
) : null}
{props.shouldSyncFromGlobal ? (
<ListItem
title={Locale.Mask.Config.Sync.Title}
subTitle={Locale.Mask.Config.Sync.SubTitle}
>
<input
type="checkbox"
checked={props.mask.syncGlobalConfig}
onChange={async (e) => {
const checked = e.currentTarget.checked;
if (
checked &&
(await showConfirm(Locale.Mask.Config.Sync.Confirm))
) {
props.updateMask((mask) => {
mask.syncGlobalConfig = checked;
mask.modelConfig = { ...globalConfig.modelConfig };
});
} else if (!checked) {
props.updateMask((mask) => {
mask.syncGlobalConfig = checked;
});
}
}}
></input>
</ListItem>
) : null}
</List>
<List>
@@ -108,6 +207,7 @@ export function MaskConfig(props: {
}
function ContextPromptItem(props: {
index: number;
prompt: ChatMessage;
update: (prompt: ChatMessage) => void;
remove: () => void;
@@ -117,22 +217,27 @@ function ContextPromptItem(props: {
return (
<div className={chatStyle["context-prompt-row"]}>
{!focusingInput && (
<Select
value={props.prompt.role}
className={chatStyle["context-role"]}
onChange={(e) =>
props.update({
...props.prompt,
role: e.target.value as any,
})
}
>
{ROLES.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</Select>
<>
<div className={chatStyle["context-drag"]}>
<DragIcon />
</div>
<Select
value={props.prompt.role}
className={chatStyle["context-role"]}
onChange={(e) =>
props.update({
...props.prompt,
role: e.target.value as any,
})
}
>
{ROLES.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</Select>
</>
)}
<Input
value={props.prompt.content}
@@ -140,7 +245,12 @@ function ContextPromptItem(props: {
className={chatStyle["context-content"]}
rows={focusingInput ? 5 : 1}
onFocus={() => setFocusingInput(true)}
onBlur={() => setFocusingInput(false)}
onBlur={() => {
setFocusingInput(false);
// If the selection is not removed when the user loses focus, some
// extensions like "Translate" will always display a floating bar
window?.getSelection()?.removeAllRanges();
}}
onInput={(e) =>
props.update({
...props.prompt,
@@ -166,8 +276,8 @@ export function ContextPrompts(props: {
}) {
const context = props.context;
const addContextPrompt = (prompt: ChatMessage) => {
props.updateContext((context) => context.push(prompt));
const addContextPrompt = (prompt: ChatMessage, i: number) => {
props.updateContext((context) => context.splice(i, 0, prompt));
};
const removeContextPrompt = (i: number) => {
@@ -178,33 +288,90 @@ export function ContextPrompts(props: {
props.updateContext((context) => (context[i] = prompt));
};
const onDragEnd: OnDragEndResponder = (result) => {
if (!result.destination) {
return;
}
const newContext = reorder(
context,
result.source.index,
result.destination.index,
);
props.updateContext((context) => {
context.splice(0, context.length, ...newContext);
});
};
return (
<>
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
{context.map((c, i) => (
<ContextPromptItem
key={i}
prompt={c}
update={(prompt) => updateContextPrompt(i, prompt)}
remove={() => removeContextPrompt(i)}
/>
))}
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="context-prompt-list">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{context.map((c, i) => (
<Draggable
draggableId={c.id || i.toString()}
index={i}
key={c.id}
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ContextPromptItem
index={i}
prompt={c}
update={(prompt) => updateContextPrompt(i, prompt)}
remove={() => removeContextPrompt(i)}
/>
<div
className={chatStyle["context-prompt-insert"]}
onClick={() => {
addContextPrompt(
createMessage({
role: "user",
content: "",
date: new Date().toLocaleString(),
}),
i + 1,
);
}}
>
<AddIcon />
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<div className={chatStyle["context-prompt-row"]}>
<IconButton
icon={<AddIcon />}
text={Locale.Context.Add}
bordered
className={chatStyle["context-prompt-button"]}
onClick={() =>
addContextPrompt({
role: "user",
content: "",
date: "",
})
}
/>
</div>
{props.context.length === 0 && (
<div className={chatStyle["context-prompt-row"]}>
<IconButton
icon={<AddIcon />}
text={Locale.Context.Add}
bordered
className={chatStyle["context-prompt-button"]}
onClick={() =>
addContextPrompt(
createMessage({
role: "user",
content: "",
date: "",
}),
props.context.length,
)
}
/>
</div>
)}
</div>
</>
);
@@ -226,24 +393,26 @@ export function MaskPage() {
const [searchText, setSearchText] = useState("");
const masks = searchText.length > 0 ? searchMasks : allMasks;
// simple search, will refactor later
// refactored already, now it accurate
const onSearch = (text: string) => {
setSearchText(text);
if (text.length > 0) {
const result = allMasks.filter((m) => m.name.includes(text));
const result = allMasks.filter((m) =>
m.name.toLowerCase().includes(text.toLowerCase())
);
setSearchMasks(result);
} else {
setSearchMasks(allMasks);
}
};
const [editingMaskId, setEditingMaskId] = useState<number | undefined>();
const [editingMaskId, setEditingMaskId] = useState<string | undefined>();
const editingMask =
maskStore.get(editingMaskId) ?? BUILTIN_MASK_STORE.get(editingMaskId);
const closeMaskModal = () => setEditingMaskId(undefined);
const downloadAll = () => {
downloadAs(JSON.stringify(masks), FileName.Masks);
downloadAs(JSON.stringify(masks.filter((v) => !v.builtin)), FileName.Masks);
};
const importFromFile = () => {
@@ -256,6 +425,11 @@ export function MaskPage() {
maskStore.create(mask);
}
}
return;
}
//if the content is a single mask.
if (importMasks.name) {
maskStore.create(importMasks);
}
} catch {}
});
@@ -280,11 +454,13 @@ export function MaskPage() {
icon={<DownloadIcon />}
bordered
onClick={downloadAll}
text={Locale.UI.Export}
/>
</div>
<div className="window-action-button">
<IconButton
icon={<UploadIcon />}
text={Locale.UI.Import}
bordered
onClick={() => importFromFile()}
/>
@@ -325,7 +501,7 @@ export function MaskPage() {
</option>
{AllLangs.map((lang) => (
<option value={lang} key={lang}>
{Locale.Settings.Lang.Options[lang]}
{ALL_LANG_OPTIONS[lang]}
</option>
))}
</Select>
@@ -353,7 +529,7 @@ export function MaskPage() {
<div className={styles["mask-name"]}>{m.name}</div>
<div className={styles["mask-info"] + " one-line"}>
{`${Locale.Mask.Item.Info(m.context.length)} / ${
Locale.Settings.Lang.Options[m.lang]
ALL_LANG_OPTIONS[m.lang]
} / ${m.modelConfig.model}`}
</div>
</div>
@@ -384,8 +560,8 @@ export function MaskPage() {
<IconButton
icon={<DeleteIcon />}
text={Locale.Mask.Item.Delete}
onClick={() => {
if (confirm(Locale.Mask.Item.DeleteConfirm)) {
onClick={async () => {
if (await showConfirm(Locale.Mask.Item.DeleteConfirm)) {
maskStore.delete(m.id);
}
}}
@@ -432,7 +608,7 @@ export function MaskPage() {
<MaskConfig
mask={editingMask}
updateMask={(updater) =>
maskStore.update(editingMaskId!, updater)
maskStore.updateMask(editingMaskId!, updater)
}
readonly={editingMask.builtin}
/>

View File

@@ -0,0 +1,76 @@
.message-selector {
.message-filter {
display: flex;
.search-bar {
max-width: unset;
flex-grow: 1;
margin-right: 10px;
}
.actions {
display: flex;
button:not(:last-child) {
margin-right: 10px;
}
}
@media screen and (max-width: 600px) {
flex-direction: column;
.search-bar {
margin-right: 0;
}
.actions {
margin-top: 20px;
button {
flex-grow: 1;
}
}
}
}
.messages {
margin-top: 20px;
border-radius: 10px;
border: var(--border-in-light);
overflow: hidden;
.message {
display: flex;
align-items: center;
padding: 8px 10px;
cursor: pointer;
&-selected {
background-color: var(--second);
}
&:not(:last-child) {
border-bottom: var(--border-in-light);
}
.avatar {
margin-right: 10px;
}
.body {
flex-grow: 1;
max-width: calc(100% - 40px);
.date {
font-size: 12px;
line-height: 1.2;
opacity: 0.5;
}
.content {
font-size: 12px;
}
}
}
}
}

View File

@@ -0,0 +1,215 @@
import { useEffect, useState } from "react";
import { ChatMessage, useAppConfig, useChatStore } from "../store";
import { Updater } from "../typing";
import { IconButton } from "./button";
import { Avatar } from "./emoji";
import { MaskAvatar } from "./mask";
import Locale from "../locales";
import styles from "./message-selector.module.scss";
function useShiftRange() {
const [startIndex, setStartIndex] = useState<number>();
const [endIndex, setEndIndex] = useState<number>();
const [shiftDown, setShiftDown] = useState(false);
const onClickIndex = (index: number) => {
if (shiftDown && startIndex !== undefined) {
setEndIndex(index);
} else {
setStartIndex(index);
setEndIndex(undefined);
}
};
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key !== "Shift") return;
setShiftDown(true);
};
const onKeyUp = (e: KeyboardEvent) => {
if (e.key !== "Shift") return;
setShiftDown(false);
setStartIndex(undefined);
setEndIndex(undefined);
};
window.addEventListener("keyup", onKeyUp);
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keyup", onKeyUp);
window.removeEventListener("keydown", onKeyDown);
};
}, []);
return {
onClickIndex,
startIndex,
endIndex,
};
}
export function useMessageSelector() {
const [selection, setSelection] = useState(new Set<string>());
const updateSelection: Updater<Set<string>> = (updater) => {
const newSelection = new Set<string>(selection);
updater(newSelection);
setSelection(newSelection);
};
return {
selection,
updateSelection,
};
}
export function MessageSelector(props: {
selection: Set<string>;
updateSelection: Updater<Set<string>>;
defaultSelectAll?: boolean;
onSelected?: (messages: ChatMessage[]) => void;
}) {
const chatStore = useChatStore();
const session = chatStore.currentSession();
const isValid = (m: ChatMessage) => m.content && !m.isError && !m.streaming;
const messages = session.messages.filter(
(m, i) =>
m.id && // message must have id
isValid(m) &&
(i >= session.messages.length - 1 || isValid(session.messages[i + 1])),
);
const messageCount = messages.length;
const config = useAppConfig();
const [searchInput, setSearchInput] = useState("");
const [searchIds, setSearchIds] = useState(new Set<string>());
const isInSearchResult = (id: string) => {
return searchInput.length === 0 || searchIds.has(id);
};
const doSearch = (text: string) => {
const searchResults = new Set<string>();
if (text.length > 0) {
messages.forEach((m) =>
m.content.includes(text) ? searchResults.add(m.id!) : null,
);
}
setSearchIds(searchResults);
};
// for range selection
const { startIndex, endIndex, onClickIndex } = useShiftRange();
const selectAll = () => {
props.updateSelection((selection) =>
messages.forEach((m) => selection.add(m.id!)),
);
};
useEffect(() => {
if (props.defaultSelectAll) {
selectAll();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (startIndex === undefined || endIndex === undefined) {
return;
}
const [start, end] = [startIndex, endIndex].sort((a, b) => a - b);
props.updateSelection((selection) => {
for (let i = start; i <= end; i += 1) {
selection.add(messages[i].id ?? i);
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [startIndex, endIndex]);
const LATEST_COUNT = 4;
return (
<div className={styles["message-selector"]}>
<div className={styles["message-filter"]}>
<input
type="text"
placeholder={Locale.Select.Search}
className={styles["filter-item"] + " " + styles["search-bar"]}
value={searchInput}
onInput={(e) => {
setSearchInput(e.currentTarget.value);
doSearch(e.currentTarget.value);
}}
></input>
<div className={styles["actions"]}>
<IconButton
text={Locale.Select.All}
bordered
className={styles["filter-item"]}
onClick={selectAll}
/>
<IconButton
text={Locale.Select.Latest}
bordered
className={styles["filter-item"]}
onClick={() =>
props.updateSelection((selection) => {
selection.clear();
messages
.slice(messageCount - LATEST_COUNT)
.forEach((m) => selection.add(m.id!));
})
}
/>
<IconButton
text={Locale.Select.Clear}
bordered
className={styles["filter-item"]}
onClick={() =>
props.updateSelection((selection) => selection.clear())
}
/>
</div>
</div>
<div className={styles["messages"]}>
{messages.map((m, i) => {
if (!isInSearchResult(m.id!)) return null;
return (
<div
className={`${styles["message"]} ${
props.selection.has(m.id!) && styles["message-selected"]
}`}
key={i}
onClick={() => {
props.updateSelection((selection) => {
const id = m.id ?? i;
selection.has(id) ? selection.delete(id) : selection.add(id);
});
onClickIndex(i);
}}
>
<div className={styles["avatar"]}>
{m.role === "user" ? (
<Avatar avatar={config.avatar}></Avatar>
) : (
<MaskAvatar mask={session.mask} />
)}
</div>
<div className={styles["body"]}>
<div className={styles["date"]}>
{new Date(m.date).toLocaleString()}
</div>
<div className={`${styles["content"]} one-line`}>
{m.content}
</div>
</div>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -1,13 +1,16 @@
import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
import { ModalConfigValidator, ModelConfig } from "../store";
import Locale from "../locales";
import { InputRange } from "./input-range";
import { List, ListItem, Select } from "./ui-lib";
import { ListItem, Select } from "./ui-lib";
import { useAllModels } from "../utils/hooks";
export function ModelConfigList(props: {
modelConfig: ModelConfig;
updateConfig: (updater: (config: ModelConfig) => void) => void;
}) {
const allModels = useAllModels();
return (
<>
<ListItem title={Locale.Settings.Model}>
@@ -22,8 +25,8 @@ export function ModelConfigList(props: {
);
}}
>
{ALL_MODELS.map((v) => (
<option value={v.name} key={v.name} disabled={!v.available}>
{allModels.map((v, i) => (
<option value={v.name} key={i} disabled={!v.available}>
{v.name}
</option>
))}
@@ -48,14 +51,33 @@ export function ModelConfigList(props: {
}}
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.TopP.Title}
subTitle={Locale.Settings.TopP.SubTitle}
>
<InputRange
value={(props.modelConfig.top_p ?? 1).toFixed(1)}
min="0"
max="1"
step="0.1"
onChange={(e) => {
props.updateConfig(
(config) =>
(config.top_p = ModalConfigValidator.top_p(
e.currentTarget.valueAsNumber,
)),
);
}}
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.MaxTokens.Title}
subTitle={Locale.Settings.MaxTokens.SubTitle}
>
<input
type="number"
min={100}
max={32000}
min={1024}
max={512000}
value={props.modelConfig.max_tokens}
onChange={(e) =>
props.updateConfig(
@@ -88,6 +110,58 @@ export function ModelConfigList(props: {
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.FrequencyPenalty.Title}
subTitle={Locale.Settings.FrequencyPenalty.SubTitle}
>
<InputRange
value={props.modelConfig.frequency_penalty?.toFixed(1)}
min="-2"
max="2"
step="0.1"
onChange={(e) => {
props.updateConfig(
(config) =>
(config.frequency_penalty =
ModalConfigValidator.frequency_penalty(
e.currentTarget.valueAsNumber,
)),
);
}}
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.InjectSystemPrompts.Title}
subTitle={Locale.Settings.InjectSystemPrompts.SubTitle}
>
<input
type="checkbox"
checked={props.modelConfig.enableInjectSystemPrompts}
onChange={(e) =>
props.updateConfig(
(config) =>
(config.enableInjectSystemPrompts = e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.InputTemplate.Title}
subTitle={Locale.Settings.InputTemplate.SubTitle}
>
<input
type="text"
value={props.modelConfig.template}
onChange={(e) =>
props.updateConfig(
(config) => (config.template = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.HistoryCount.Title}
subTitle={Locale.Settings.HistoryCount.SubTitle}
@@ -96,7 +170,7 @@ export function ModelConfigList(props: {
title={props.modelConfig.historyMessageCount.toString()}
value={props.modelConfig.historyMessageCount}
min="0"
max="32"
max="64"
step="1"
onChange={(e) =>
props.updateConfig(

View File

@@ -54,13 +54,13 @@
.actions {
margin-top: 5vh;
margin-bottom: 5vh;
margin-bottom: 2vh;
animation: slide-in ease 0.45s;
display: flex;
justify-content: center;
font-size: 12px;
.more {
font-size: 12px;
.skip {
margin-left: 10px;
}
}
@@ -68,16 +68,26 @@
.masks {
flex-grow: 1;
width: 100%;
overflow: hidden;
overflow: auto;
align-items: center;
padding-top: 20px;
$linear: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 0)
);
-webkit-mask-image: $linear;
mask-image: $linear;
animation: slide-in ease 0.5s;
.mask-row {
margin-bottom: 10px;
display: flex;
justify-content: center;
// justify-content: center;
margin-bottom: 10px;
@for $i from 1 to 10 {
&:nth-child(#{$i * 2}) {

View File

@@ -14,6 +14,8 @@ import Locale from "../locales";
import { useAppConfig, useChatStore } from "../store";
import { MaskAvatar } from "./mask";
import { useCommand } from "../command";
import { showConfirm } from "./ui-lib";
import { BUILTIN_MASK_STORE } from "../masks";
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
const xmin = Math.max(aRect.x, bRect.x);
@@ -27,32 +29,8 @@ function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
}
function MaskItem(props: { mask: Mask; onClick?: () => void }) {
const domRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const changeOpacity = () => {
const dom = domRef.current;
const parent = document.getElementById(SlotID.AppBody);
if (!parent || !dom) return;
const domRect = dom.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
const intersectionArea = getIntersectionArea(domRect, parentRect);
const domArea = domRect.width * domRect.height;
const ratio = intersectionArea / domArea;
const opacity = ratio > 0.9 ? 1 : 0.4;
dom.style.opacity = opacity.toString();
};
setTimeout(changeOpacity, 30);
window.addEventListener("resize", changeOpacity);
return () => window.removeEventListener("resize", changeOpacity);
}, [domRef]);
return (
<div className={styles["mask"]} ref={domRef} onClick={props.onClick}>
<div className={styles["mask"]} onClick={props.onClick}>
<MaskAvatar mask={props.mask} />
<div className={styles["mask-name"] + " one-line"}>{props.mask.name}</div>
</div>
@@ -63,32 +41,38 @@ function useMaskGroup(masks: Mask[]) {
const [groups, setGroups] = useState<Mask[][]>([]);
useEffect(() => {
const appBody = document.getElementById(SlotID.AppBody);
if (!appBody || masks.length === 0) return;
const computeGroup = () => {
const appBody = document.getElementById(SlotID.AppBody);
if (!appBody || masks.length === 0) return;
const rect = appBody.getBoundingClientRect();
const maxWidth = rect.width;
const maxHeight = rect.height * 0.6;
const maskItemWidth = 120;
const maskItemHeight = 50;
const rect = appBody.getBoundingClientRect();
const maxWidth = rect.width;
const maxHeight = rect.height * 0.6;
const maskItemWidth = 120;
const maskItemHeight = 50;
const randomMask = () => masks[Math.floor(Math.random() * masks.length)];
let maskIndex = 0;
const nextMask = () => masks[maskIndex++ % masks.length];
const randomMask = () => masks[Math.floor(Math.random() * masks.length)];
let maskIndex = 0;
const nextMask = () => masks[maskIndex++ % masks.length];
const rows = Math.ceil(maxHeight / maskItemHeight);
const cols = Math.ceil(maxWidth / maskItemWidth);
const rows = Math.ceil(maxHeight / maskItemHeight);
const cols = Math.ceil(maxWidth / maskItemWidth);
const newGroups = new Array(rows)
.fill(0)
.map((_, _i) =>
new Array(cols)
.fill(0)
.map((_, j) => (j < 1 || j > cols - 2 ? randomMask() : nextMask())),
);
const newGroups = new Array(rows)
.fill(0)
.map((_, _i) =>
new Array(cols)
.fill(0)
.map((_, j) => (j < 1 || j > cols - 2 ? randomMask() : nextMask())),
);
setGroups(newGroups);
setGroups(newGroups);
};
computeGroup();
window.addEventListener("resize", computeGroup);
return () => window.removeEventListener("resize", computeGroup);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -105,17 +89,21 @@ export function NewChat() {
const navigate = useNavigate();
const config = useAppConfig();
const maskRef = useRef<HTMLDivElement>(null);
const { state } = useLocation();
const startChat = (mask?: Mask) => {
chatStore.newSession(mask);
setTimeout(() => navigate(Path.Chat), 1);
setTimeout(() => {
chatStore.newSession(mask);
navigate(Path.Chat);
}, 10);
};
useCommand({
mask: (id) => {
try {
const mask = maskStore.get(parseInt(id));
const mask = maskStore.get(id) ?? BUILTIN_MASK_STORE.get(id);
startChat(mask ?? undefined);
} catch {
console.error("[New Chat] failed to create chat from mask id=", id);
@@ -123,6 +111,13 @@ export function NewChat() {
},
});
useEffect(() => {
if (maskRef.current) {
maskRef.current.scrollLeft =
(maskRef.current.scrollWidth - maskRef.current.clientWidth) / 2;
}
}, [groups]);
return (
<div className={styles["new-chat"]}>
<div className={styles["mask-header"]}>
@@ -134,8 +129,8 @@ export function NewChat() {
{!state?.fromHome && (
<IconButton
text={Locale.NewChat.NotShow}
onClick={() => {
if (confirm(Locale.NewChat.ConfirmNoShow)) {
onClick={async () => {
if (await showConfirm(Locale.NewChat.ConfirmNoShow)) {
startChat();
config.update(
(config) => (config.dontShowMaskSplashScreen = true),
@@ -162,24 +157,24 @@ export function NewChat() {
<div className={styles["actions"]}>
<IconButton
text={Locale.NewChat.Skip}
onClick={() => startChat()}
icon={<LightningIcon />}
type="primary"
shadow
/>
<IconButton
className={styles["more"]}
text={Locale.NewChat.More}
onClick={() => navigate(Path.Masks)}
icon={<EyeIcon />}
bordered
shadow
/>
<IconButton
text={Locale.NewChat.Skip}
onClick={() => startChat()}
icon={<LightningIcon />}
type="primary"
shadow
className={styles["skip"]}
/>
</div>
<div className={styles["masks"]}>
<div className={styles["masks"]} ref={maskRef}>
{groups.map((masks, i) => (
<div key={i} className={styles["mask-row"]}>
{masks.map((mask, index) => (

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
import { useState, useEffect, useMemo } from "react";
import styles from "./settings.module.scss";
@@ -10,6 +10,15 @@ import ClearIcon from "../icons/clear.svg";
import LoadingIcon from "../icons/three-dots.svg";
import EditIcon from "../icons/edit.svg";
import EyeIcon from "../icons/eye.svg";
import DownloadIcon from "../icons/download.svg";
import UploadIcon from "../icons/upload.svg";
import ConfigIcon from "../icons/config.svg";
import ConfirmIcon from "../icons/confirm.svg";
import ConnectionIcon from "../icons/connection.svg";
import CloudSuccessIcon from "../icons/cloud-success.svg";
import CloudFailIcon from "../icons/cloud-fail.svg";
import {
Input,
List,
@@ -18,6 +27,8 @@ import {
PasswordInput,
Popover,
Select,
showConfirm,
showToast,
} from "./ui-lib";
import { ModelConfigList } from "./model-config";
@@ -31,17 +42,36 @@ import {
useAppConfig,
} from "../store";
import Locale, { AllLangs, changeLang, getLang } from "../locales";
import Locale, {
AllLangs,
ALL_LANG_OPTIONS,
changeLang,
getLang,
} from "../locales";
import { copyToClipboard } from "../utils";
import Link from "next/link";
import { Path, UPDATE_URL } from "../constant";
import {
Azure,
OPENAI_BASE_URL,
Path,
RELEASE_URL,
STORAGE_KEY,
ServiceProvider,
SlotID,
UPDATE_URL,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
import { InputRange } from "./input-range";
import { useNavigate } from "react-router-dom";
import { Avatar, AvatarPicker } from "./emoji";
import { getClientConfig } from "../config/client";
import { useSyncStore } from "../store/sync";
import { nanoid } from "nanoid";
import { useMaskStore } from "../store/mask";
import { ProviderType } from "../utils/cloud";
function EditPromptModal(props: { id: number; onClose: () => void }) {
function EditPromptModal(props: { id: string; onClose: () => void }) {
const promptStore = usePromptStore();
const prompt = promptStore.get(props.id);
@@ -66,7 +96,7 @@ function EditPromptModal(props: { id: number; onClose: () => void }) {
readOnly={!prompt.isUser}
className={styles["edit-prompt-title"]}
onInput={(e) =>
promptStore.update(
promptStore.updatePrompt(
props.id,
(prompt) => (prompt.title = e.currentTarget.value),
)
@@ -78,7 +108,7 @@ function EditPromptModal(props: { id: number; onClose: () => void }) {
className={styles["edit-prompt-content"]}
rows={10}
onInput={(e) =>
promptStore.update(
promptStore.updatePrompt(
props.id,
(prompt) => (prompt.content = e.currentTarget.value),
)
@@ -99,7 +129,7 @@ function UserPromptModal(props: { onClose?: () => void }) {
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
const [editingPromptId, setEditingPromptId] = useState<number>();
const [editingPromptId, setEditingPromptId] = useState<string>();
useEffect(() => {
if (searchInput.length > 0) {
@@ -118,12 +148,15 @@ function UserPromptModal(props: { onClose?: () => void }) {
actions={[
<IconButton
key="add"
onClick={() =>
promptStore.add({
onClick={() => {
const promptId = promptStore.add({
id: nanoid(),
createdAt: Date.now(),
title: "Empty Prompt",
content: "Empty Prompt Content",
})
}
});
setEditingPromptId(promptId);
}}
icon={<AddIcon />}
bordered
text={Locale.Settings.Prompt.Modal.Add}
@@ -192,17 +225,336 @@ function UserPromptModal(props: { onClose?: () => void }) {
);
}
function formatVersionDate(t: string) {
const d = new Date(+t);
const year = d.getUTCFullYear();
const month = d.getUTCMonth() + 1;
const day = d.getUTCDate();
function DangerItems() {
const chatStore = useChatStore();
const appConfig = useAppConfig();
return [
year.toString(),
month.toString().padStart(2, "0"),
day.toString().padStart(2, "0"),
].join("");
return (
<List>
<ListItem
title={Locale.Settings.Danger.Reset.Title}
subTitle={Locale.Settings.Danger.Reset.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Reset.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
appConfig.reset();
}
}}
type="danger"
/>
</ListItem>
<ListItem
title={Locale.Settings.Danger.Clear.Title}
subTitle={Locale.Settings.Danger.Clear.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Clear.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
chatStore.clearAllData();
}
}}
type="danger"
/>
</ListItem>
</List>
);
}
function CheckButton() {
const syncStore = useSyncStore();
const couldCheck = useMemo(() => {
return syncStore.coundSync();
}, [syncStore]);
const [checkState, setCheckState] = useState<
"none" | "checking" | "success" | "failed"
>("none");
async function check() {
setCheckState("checking");
const valid = await syncStore.check();
setCheckState(valid ? "success" : "failed");
}
if (!couldCheck) return null;
return (
<IconButton
text={Locale.Settings.Sync.Config.Modal.Check}
bordered
onClick={check}
icon={
checkState === "none" ? (
<ConnectionIcon />
) : checkState === "checking" ? (
<LoadingIcon />
) : checkState === "success" ? (
<CloudSuccessIcon />
) : checkState === "failed" ? (
<CloudFailIcon />
) : (
<ConnectionIcon />
)
}
></IconButton>
);
}
function SyncConfigModal(props: { onClose?: () => void }) {
const syncStore = useSyncStore();
return (
<div className="modal-mask">
<Modal
title={Locale.Settings.Sync.Config.Modal.Title}
onClose={() => props.onClose?.()}
actions={[
<CheckButton key="check" />,
<IconButton
key="confirm"
onClick={props.onClose}
icon={<ConfirmIcon />}
bordered
text={Locale.UI.Confirm}
/>,
]}
>
<List>
<ListItem
title={Locale.Settings.Sync.Config.SyncType.Title}
subTitle={Locale.Settings.Sync.Config.SyncType.SubTitle}
>
<select
value={syncStore.provider}
onChange={(e) => {
syncStore.update(
(config) =>
(config.provider = e.target.value as ProviderType),
);
}}
>
{Object.entries(ProviderType).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</select>
</ListItem>
<ListItem
title={Locale.Settings.Sync.Config.Proxy.Title}
subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle}
>
<input
type="checkbox"
checked={syncStore.useProxy}
onChange={(e) => {
syncStore.update(
(config) => (config.useProxy = e.currentTarget.checked),
);
}}
></input>
</ListItem>
{syncStore.useProxy ? (
<ListItem
title={Locale.Settings.Sync.Config.ProxyUrl.Title}
subTitle={Locale.Settings.Sync.Config.ProxyUrl.SubTitle}
>
<input
type="text"
value={syncStore.proxyUrl}
onChange={(e) => {
syncStore.update(
(config) => (config.proxyUrl = e.currentTarget.value),
);
}}
></input>
</ListItem>
) : null}
</List>
{syncStore.provider === ProviderType.WebDAV && (
<>
<List>
<ListItem title={Locale.Settings.Sync.Config.WebDav.Endpoint}>
<input
type="text"
value={syncStore.webdav.endpoint}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.endpoint = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.WebDav.UserName}>
<input
type="text"
value={syncStore.webdav.username}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.username = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.WebDav.Password}>
<PasswordInput
value={syncStore.webdav.password}
onChange={(e) => {
syncStore.update(
(config) =>
(config.webdav.password = e.currentTarget.value),
);
}}
></PasswordInput>
</ListItem>
</List>
</>
)}
{syncStore.provider === ProviderType.UpStash && (
<List>
<ListItem title={Locale.Settings.Sync.Config.UpStash.Endpoint}>
<input
type="text"
value={syncStore.upstash.endpoint}
onChange={(e) => {
syncStore.update(
(config) =>
(config.upstash.endpoint = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.UpStash.UserName}>
<input
type="text"
value={syncStore.upstash.username}
placeholder={STORAGE_KEY}
onChange={(e) => {
syncStore.update(
(config) =>
(config.upstash.username = e.currentTarget.value),
);
}}
></input>
</ListItem>
<ListItem title={Locale.Settings.Sync.Config.UpStash.Password}>
<PasswordInput
value={syncStore.upstash.apiKey}
onChange={(e) => {
syncStore.update(
(config) => (config.upstash.apiKey = e.currentTarget.value),
);
}}
></PasswordInput>
</ListItem>
</List>
)}
</Modal>
</div>
);
}
function SyncItems() {
const syncStore = useSyncStore();
const chatStore = useChatStore();
const promptStore = usePromptStore();
const maskStore = useMaskStore();
const couldSync = useMemo(() => {
return syncStore.coundSync();
}, [syncStore]);
const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);
const stateOverview = useMemo(() => {
const sessions = chatStore.sessions;
const messageCount = sessions.reduce((p, c) => p + c.messages.length, 0);
return {
chat: sessions.length,
message: messageCount,
prompt: Object.keys(promptStore.prompts).length,
mask: Object.keys(maskStore.masks).length,
};
}, [chatStore.sessions, maskStore.masks, promptStore.prompts]);
return (
<>
<List>
<ListItem
title={Locale.Settings.Sync.CloudState}
subTitle={
syncStore.lastProvider
? `${new Date(syncStore.lastSyncTime).toLocaleString()} [${
syncStore.lastProvider
}]`
: Locale.Settings.Sync.NotSyncYet
}
>
<div style={{ display: "flex" }}>
<IconButton
icon={<ConfigIcon />}
text={Locale.UI.Config}
onClick={() => {
setShowSyncConfigModal(true);
}}
/>
{couldSync && (
<IconButton
icon={<ResetIcon />}
text={Locale.UI.Sync}
onClick={async () => {
try {
await syncStore.sync();
showToast(Locale.Settings.Sync.Success);
} catch (e) {
showToast(Locale.Settings.Sync.Fail);
console.error("[Sync]", e);
}
}}
/>
)}
</div>
</ListItem>
<ListItem
title={Locale.Settings.Sync.LocalState}
subTitle={Locale.Settings.Sync.Overview(stateOverview)}
>
<div style={{ display: "flex" }}>
<IconButton
icon={<UploadIcon />}
text={Locale.UI.Export}
onClick={() => {
syncStore.export();
}}
/>
<IconButton
icon={<DownloadIcon />}
text={Locale.UI.Import}
onClick={() => {
syncStore.import();
}}
/>
</div>
</ListItem>
</List>
{showSyncConfigModal && (
<SyncConfigModal onClose={() => setShowSyncConfigModal(false)} />
)}
</>
);
}
export function Settings() {
@@ -210,14 +562,13 @@ export function Settings() {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const config = useAppConfig();
const updateConfig = config.update;
const resetConfig = config.reset;
const chatStore = useChatStore();
const updateStore = useUpdateStore();
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentVersion = formatVersionDate(updateStore.version);
const remoteId = formatVersionDate(updateStore.remoteVersion);
const currentVersion = updateStore.formatVersion(updateStore.version);
const remoteId = updateStore.formatVersion(updateStore.remoteVersion);
const hasNewVersion = currentVersion !== remoteId;
const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;
function checkUpdate(force = false) {
setCheckingUpdate(true);
@@ -225,29 +576,40 @@ export function Settings() {
setCheckingUpdate(false);
});
console.log(
"[Update] local version ",
new Date(+updateStore.version).toLocaleString(),
);
console.log(
"[Update] remote version ",
new Date(+updateStore.remoteVersion).toLocaleString(),
);
console.log("[Update] local version ", updateStore.version);
console.log("[Update] remote version ", updateStore.remoteVersion);
}
const accessStore = useAccessStore();
const shouldHideBalanceQuery = useMemo(() => {
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
return (
accessStore.hideBalanceQuery ||
isOpenAiUrl ||
accessStore.provider === ServiceProvider.Azure
);
}, [
accessStore.hideBalanceQuery,
accessStore.openaiUrl,
accessStore.provider,
]);
const usage = {
used: updateStore.used,
subscription: updateStore.subscription,
};
const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage(force = false) {
if (shouldHideBalanceQuery) {
return;
}
setLoadingUsage(true);
updateStore.updateUsage(force).finally(() => {
setLoadingUsage(false);
});
}
const accessStore = useAccessStore();
const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(),
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -280,9 +642,12 @@ export function Settings() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const clientConfig = useMemo(() => getClientConfig(), []);
const showAccessCode = enabledAccessControl && !clientConfig?.isApp;
return (
<ErrorBoundary>
<div className="window-header">
<div className="window-header" data-tauri-drag-region>
<div className="window-header-title">
<div className="window-header-main-title">
{Locale.Settings.Title}
@@ -292,36 +657,13 @@ export function Settings() {
</div>
</div>
<div className="window-actions">
<div className="window-action-button">
<IconButton
icon={<ClearIcon />}
onClick={() => {
if (confirm(Locale.Settings.Actions.ConfirmClearAll)) {
chatStore.clearAllData();
}
}}
bordered
title={Locale.Settings.Actions.ClearAll}
/>
</div>
<div className="window-action-button">
<IconButton
icon={<ResetIcon />}
onClick={() => {
if (confirm(Locale.Settings.Actions.ConfirmResetAll)) {
resetConfig();
}
}}
bordered
title={Locale.Settings.Actions.ResetAll}
/>
</div>
<div className="window-action-button"></div>
<div className="window-action-button"></div>
<div className="window-action-button">
<IconButton
icon={<CloseIcon />}
onClick={() => navigate(Path.Home)}
bordered
title={Locale.Settings.Actions.Close}
/>
</div>
</div>
@@ -363,7 +705,7 @@ export function Settings() {
{checkingUpdate ? (
<LoadingIcon />
) : hasNewVersion ? (
<Link href={UPDATE_URL} target="_blank" className="link">
<Link href={updateUrl} target="_blank" className="link">
{Locale.Settings.Update.GoToUpdate}
</Link>
) : (
@@ -419,7 +761,7 @@ export function Settings() {
>
{AllLangs.map((lang) => (
<option value={lang} key={lang}>
{Locale.Settings.Lang.Options[lang]}
{ALL_LANG_OPTIONS[lang]}
</option>
))}
</Select>
@@ -433,7 +775,7 @@ export function Settings() {
title={`${config.fontSize ?? 14}px`}
value={config.fontSize}
min="12"
max="18"
max="40"
step="1"
onChange={(e) =>
updateConfig(
@@ -444,6 +786,22 @@ export function Settings() {
></InputRange>
</ListItem>
<ListItem
title={Locale.Settings.AutoGenerateTitle.Title}
subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}
>
<input
type="checkbox"
checked={config.enableAutoGenerateTitle}
onChange={(e) =>
updateConfig(
(config) =>
(config.enableAutoGenerateTitle = e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.SendPreviewBubble.Title}
subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
@@ -459,10 +817,14 @@ export function Settings() {
}
></input>
</ListItem>
</List>
<SyncItems />
<List>
<ListItem
title={Locale.Settings.Mask.Title}
subTitle={Locale.Settings.Mask.SubTitle}
title={Locale.Settings.Mask.Splash.Title}
subTitle={Locale.Settings.Mask.Splash.SubTitle}
>
<input
type="checkbox"
@@ -476,65 +838,21 @@ export function Settings() {
}
></input>
</ListItem>
</List>
<List>
{enabledAccessControl ? (
<ListItem
title={Locale.Settings.AccessCode.Title}
subTitle={Locale.Settings.AccessCode.SubTitle}
>
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.AccessCode.Placeholder}
onChange={(e) => {
accessStore.updateCode(e.currentTarget.value);
}}
/>
</ListItem>
) : (
<></>
)}
{!accessStore.hideUserApiKey ? (
<ListItem
title={Locale.Settings.Token.Title}
subTitle={Locale.Settings.Token.SubTitle}
>
<PasswordInput
value={accessStore.token}
type="text"
placeholder={Locale.Settings.Token.Placeholder}
onChange={(e) => {
accessStore.updateToken(e.currentTarget.value);
}}
/>
</ListItem>
) : null}
<ListItem
title={Locale.Settings.Usage.Title}
subTitle={
showUsage
? loadingUsage
? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle(
usage?.used ?? "[?]",
usage?.subscription ?? "[?]",
)
: Locale.Settings.Usage.NoAccess
}
title={Locale.Settings.Mask.Builtin.Title}
subTitle={Locale.Settings.Mask.Builtin.SubTitle}
>
{!showUsage || loadingUsage ? (
<div />
) : (
<IconButton
icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Usage.Check}
onClick={() => checkUsage(true)}
/>
)}
<input
type="checkbox"
checked={config.hideBuiltinMasks}
onChange={(e) =>
updateConfig(
(config) =>
(config.hideBuiltinMasks = e.currentTarget.checked),
)
}
></input>
</ListItem>
</List>
@@ -570,6 +888,213 @@ export function Settings() {
</ListItem>
</List>
<List id={SlotID.CustomModel}>
{showAccessCode && (
<ListItem
title={Locale.Settings.Access.AccessCode.Title}
subTitle={Locale.Settings.Access.AccessCode.SubTitle}
>
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
);
}}
/>
</ListItem>
)}
{!accessStore.hideUserApiKey && (
<>
<ListItem
title={Locale.Settings.Access.CustomEndpoint.Title}
subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
>
<input
type="checkbox"
checked={accessStore.useCustomConfig}
onChange={(e) =>
accessStore.update(
(access) =>
(access.useCustomConfig = e.currentTarget.checked),
)
}
></input>
</ListItem>
{accessStore.useCustomConfig && (
<>
<ListItem
title={Locale.Settings.Access.Provider.Title}
subTitle={Locale.Settings.Access.Provider.SubTitle}
>
<Select
value={accessStore.provider}
onChange={(e) => {
accessStore.update(
(access) =>
(access.provider = e.target
.value as ServiceProvider),
);
}}
>
{Object.entries(ServiceProvider).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</Select>
</ListItem>
{accessStore.provider === "OpenAI" ? (
<>
<ListItem
title={Locale.Settings.Access.OpenAI.Endpoint.Title}
subTitle={
Locale.Settings.Access.OpenAI.Endpoint.SubTitle
}
>
<input
type="text"
value={accessStore.openaiUrl}
placeholder={OPENAI_BASE_URL}
onChange={(e) =>
accessStore.update(
(access) =>
(access.openaiUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.OpenAI.ApiKey.Title}
subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.openaiApiKey}
type="text"
placeholder={
Locale.Settings.Access.OpenAI.ApiKey.Placeholder
}
onChange={(e) => {
accessStore.update(
(access) =>
(access.openaiApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
) : (
<>
<ListItem
title={Locale.Settings.Access.Azure.Endpoint.Title}
subTitle={
Locale.Settings.Access.Azure.Endpoint.SubTitle +
Azure.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.azureUrl}
placeholder={Azure.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) =>
(access.azureUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiKey.Title}
subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.azureApiKey}
type="text"
placeholder={
Locale.Settings.Access.Azure.ApiKey.Placeholder
}
onChange={(e) => {
accessStore.update(
(access) =>
(access.azureApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiVerion.Title}
subTitle={
Locale.Settings.Access.Azure.ApiVerion.SubTitle
}
>
<input
type="text"
value={accessStore.azureApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) =>
(access.azureApiVersion =
e.currentTarget.value),
)
}
></input>
</ListItem>
</>
)}
</>
)}
</>
)}
{!shouldHideBalanceQuery ? (
<ListItem
title={Locale.Settings.Usage.Title}
subTitle={
showUsage
? loadingUsage
? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle(
usage?.used ?? "[?]",
usage?.subscription ?? "[?]",
)
: Locale.Settings.Usage.NoAccess
}
>
{!showUsage || loadingUsage ? (
<div />
) : (
<IconButton
icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Usage.Check}
onClick={() => checkUsage(true)}
/>
)}
</ListItem>
) : null}
<ListItem
title={Locale.Settings.Access.CustomModel.Title}
subTitle={Locale.Settings.Access.CustomModel.SubTitle}
>
<input
type="text"
value={config.customModels}
placeholder="model1,model2,model3"
onChange={(e) =>
config.update(
(config) => (config.customModels = e.currentTarget.value),
)
}
></input>
</ListItem>
</List>
<List>
<ModelConfigList
modelConfig={config.modelConfig}
@@ -584,6 +1109,8 @@ export function Settings() {
{shouldShowPromptModal && (
<UserPromptModal onClose={() => setShowPromptModal(false)} />
)}
<DangerItems />
</div>
</ErrorBoundary>
);

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useMemo } from "react";
import styles from "./home.module.scss";
@@ -8,14 +8,17 @@ import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
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 Locale from "../locales";
import { useAppConfig, useChatStore } from "../store";
import {
DEFAULT_SIDEBAR_WIDTH,
MAX_SIDEBAR_WIDTH,
MIN_SIDEBAR_WIDTH,
NARROW_SIDEBAR_WIDTH,
@@ -24,9 +27,9 @@ import {
} from "../constant";
import { Link, useNavigate } from "react-router-dom";
import { useMobileScreen } from "../utils";
import { isIOS, useMobileScreen } from "../utils";
import dynamic from "next/dynamic";
import { showToast } from "./ui-lib";
import { showConfirm, showToast } from "./ui-lib";
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
loading: () => null,
@@ -37,14 +40,11 @@ function useHotKey() {
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.metaKey || e.altKey || e.ctrlKey) {
const n = chatStore.sessions.length;
const limit = (x: number) => (x + n) % n;
const i = chatStore.currentSessionIndex;
if (e.altKey || e.ctrlKey) {
if (e.key === "ArrowUp") {
chatStore.selectSession(limit(i - 1));
chatStore.nextSession(-1);
} else if (e.key === "ArrowDown") {
chatStore.selectSession(limit(i + 1));
chatStore.nextSession(1);
}
}
};
@@ -59,31 +59,57 @@ function useDragSideBar() {
const config = useAppConfig();
const startX = useRef(0);
const startDragWidth = useRef(config.sidebarWidth ?? 300);
const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
const lastUpdateTime = useRef(Date.now());
const handleMouseMove = useRef((e: MouseEvent) => {
if (Date.now() < lastUpdateTime.current + 50) {
return;
}
lastUpdateTime.current = Date.now();
const d = e.clientX - startX.current;
const nextWidth = limit(startDragWidth.current + d);
config.update((config) => (config.sidebarWidth = nextWidth));
});
const handleMouseUp = useRef(() => {
startDragWidth.current = config.sidebarWidth ?? 300;
window.removeEventListener("mousemove", handleMouseMove.current);
window.removeEventListener("mouseup", handleMouseUp.current);
});
const onDragMouseDown = (e: MouseEvent) => {
startX.current = e.clientX;
window.addEventListener("mousemove", handleMouseMove.current);
window.addEventListener("mouseup", handleMouseUp.current);
const toggleSideBar = () => {
config.update((config) => {
if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) {
config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;
} else {
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
}
});
};
const onDragStart = (e: MouseEvent) => {
// Remembers the initial width each time the mouse is pressed
startX.current = e.clientX;
startDragWidth.current = config.sidebarWidth;
const dragStartTime = Date.now();
const handleDragMove = (e: MouseEvent) => {
if (Date.now() < lastUpdateTime.current + 20) {
return;
}
lastUpdateTime.current = Date.now();
const d = e.clientX - startX.current;
const nextWidth = limit(startDragWidth.current + d);
config.update((config) => {
if (nextWidth < MIN_SIDEBAR_WIDTH) {
config.sidebarWidth = NARROW_SIDEBAR_WIDTH;
} else {
config.sidebarWidth = nextWidth;
}
});
};
const handleDragEnd = () => {
// In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth
window.removeEventListener("pointermove", handleDragMove);
window.removeEventListener("pointerup", handleDragEnd);
// if user click the drag icon, should toggle the sidebar
const shouldFireClick = Date.now() - dragStartTime < 300;
if (shouldFireClick) {
toggleSideBar();
}
};
window.addEventListener("pointermove", handleDragMove);
window.addEventListener("pointerup", handleDragEnd);
};
const isMobileScreen = useMobileScreen();
const shouldNarrow =
!isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
@@ -91,13 +117,13 @@ function useDragSideBar() {
useEffect(() => {
const barWidth = shouldNarrow
? NARROW_SIDEBAR_WIDTH
: limit(config.sidebarWidth ?? 300);
: limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);
const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
}, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
return {
onDragMouseDown,
onDragStart,
shouldNarrow,
};
}
@@ -106,9 +132,14 @@ export function SideBar(props: { className?: string }) {
const chatStore = useChatStore();
// drag side bar
const { onDragMouseDown, shouldNarrow } = useDragSideBar();
const { onDragStart, shouldNarrow } = useDragSideBar();
const navigate = useNavigate();
const config = useAppConfig();
const isMobileScreen = useMobileScreen();
const isIOSMobile = useMemo(
() => isIOS() && isMobileScreen,
[isMobileScreen],
);
useHotKey();
@@ -117,9 +148,15 @@ export function SideBar(props: { className?: string }) {
className={`${styles.sidebar} ${props.className} ${
shouldNarrow && styles["narrow-sidebar"]
}`}
style={{
// #3016 disable transition on ios mobile screen
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
}}
>
<div className={styles["sidebar-header"]}>
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
<div className={styles["sidebar-header"]} data-tauri-drag-region>
<div className={styles["sidebar-title"]} data-tauri-drag-region>
ChatGPT Next
</div>
<div className={styles["sidebar-sub-title"]}>
Build your own AI assistant.
</div>
@@ -133,7 +170,13 @@ export function SideBar(props: { className?: string }) {
icon={<MaskIcon />}
text={shouldNarrow ? undefined : Locale.Mask.Name}
className={styles["sidebar-bar-button"]}
onClick={() => navigate(Path.NewChat, { state: { fromHome: true } })}
onClick={() => {
if (config.dontShowMaskSplashScreen !== true) {
navigate(Path.NewChat, { state: { fromHome: true } });
} else {
navigate(Path.Masks, { state: { fromHome: true } });
}
}}
shadow
/>
<IconButton
@@ -160,9 +203,9 @@ export function SideBar(props: { className?: string }) {
<div className={styles["sidebar-actions"]}>
<div className={styles["sidebar-action"] + " " + styles.mobile}>
<IconButton
icon={<CloseIcon />}
onClick={() => {
if (confirm(Locale.Home.DeleteChat)) {
icon={<DeleteIcon />}
onClick={async () => {
if (await showConfirm(Locale.Home.DeleteChat)) {
chatStore.deleteSession(chatStore.currentSessionIndex);
}
}}
@@ -174,7 +217,7 @@ export function SideBar(props: { className?: string }) {
</Link>
</div>
<div className={styles["sidebar-action"]}>
<a href={REPO_URL} target="_blank">
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
<IconButton icon={<GithubIcon />} shadow />
</a>
</div>
@@ -198,8 +241,10 @@ export function SideBar(props: { className?: string }) {
<div
className={styles["sidebar-drag"]}
onMouseDown={(e) => onDragMouseDown(e as any)}
></div>
onPointerDown={(e) => onDragStart(e as any)}
>
<DragIcon />
</div>
</div>
);
}

View File

@@ -62,6 +62,7 @@
box-shadow: var(--card-shadow);
margin-bottom: 20px;
animation: slide-in ease 0.3s;
background: var(--white);
}
.list .list-item:last-child {
@@ -72,11 +73,26 @@
box-shadow: var(--card-shadow);
background-color: var(--white);
border-radius: 12px;
width: 60vw;
width: 80vw;
max-width: 900px;
min-width: 300px;
animation: slide-in ease 0.3s;
--modal-padding: 20px;
&-max {
width: 95vw;
max-width: unset;
height: 95vh;
display: flex;
flex-direction: column;
.modal-content {
max-height: unset !important;
flex-grow: 1;
}
}
.modal-header {
padding: var(--modal-padding);
display: flex;
@@ -89,11 +105,19 @@
font-size: 16px;
}
.modal-close-btn {
cursor: pointer;
.modal-header-actions {
display: flex;
&:hover {
filter: brightness(1.2);
.modal-header-action {
cursor: pointer;
&:not(:last-child) {
margin-right: 20px;
}
&:hover {
filter: brightness(1.2);
}
}
}
}
@@ -207,11 +231,11 @@
.select-with-icon {
position: relative;
max-width: fit-content;
.select-with-icon-select {
height: 100%;
border: var(--border-in-light);
padding: 10px 25px 10px 10px;
padding: 10px 35px 10px 10px;
border-radius: 10px;
appearance: none;
cursor: pointer;
@@ -227,4 +251,57 @@
transform: translateY(-50%);
pointer-events: none;
}
}
}
.modal-input {
height: 100%;
width: 100%;
border-radius: 10px;
border: var(--border-in-light);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
background-color: var(--white);
color: var(--black);
font-family: inherit;
padding: 10px;
resize: none;
outline: none;
box-sizing: border-box;
&:focus {
border: 1px solid var(--primary);
}
}
.selector {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
&-content {
.list {
max-height: 90vh;
overflow-x: hidden;
overflow-y: auto;
.list-item {
cursor: pointer;
background-color: var(--white);
&:hover {
filter: brightness(0.95);
}
&:active {
filter: brightness(0.9);
}
}
}
}
}

View File

@@ -1,9 +1,16 @@
/* eslint-disable @next/next/no-img-element */
import styles from "./ui-lib.module.scss";
import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg";
import EyeIcon from "../icons/eye.svg";
import EyeOffIcon from "../icons/eye-off.svg";
import DownIcon from "../icons/down.svg";
import ConfirmIcon from "../icons/confirm.svg";
import CancelIcon from "../icons/cancel.svg";
import MaxIcon from "../icons/max.svg";
import MinIcon from "../icons/min.svg";
import Locale from "../locales";
import { createRoot } from "react-dom/client";
import React, { HTMLProps, useEffect, useState } from "react";
@@ -40,9 +47,13 @@ export function ListItem(props: {
children?: JSX.Element | JSX.Element[];
icon?: JSX.Element;
className?: string;
onClick?: () => void;
}) {
return (
<div className={styles["list-item"] + ` ${props.className}`}>
<div
className={styles["list-item"] + ` ${props.className || ""}`}
onClick={props.onClick}
>
<div className={styles["list-header"]}>
{props.icon && <div className={styles["list-icon"]}>{props.icon}</div>}
<div className={styles["list-item-title"]}>
@@ -59,14 +70,12 @@ export function ListItem(props: {
);
}
export function List(props: {
children:
| Array<JSX.Element | null | undefined>
| JSX.Element
| null
| undefined;
}) {
return <div className={styles.list}>{props.children}</div>;
export function List(props: { children: React.ReactNode; id?: string }) {
return (
<div className={styles.list} id={props.id}>
{props.children}
</div>
);
}
export function Loading() {
@@ -87,8 +96,9 @@ export function Loading() {
interface ModalProps {
title: string;
children?: JSX.Element | JSX.Element[];
children?: any;
actions?: JSX.Element[];
defaultMax?: boolean;
onClose?: () => void;
}
export function Modal(props: ModalProps) {
@@ -107,13 +117,30 @@ export function Modal(props: ModalProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [isMax, setMax] = useState(!!props.defaultMax);
return (
<div className={styles["modal-container"]}>
<div
className={
styles["modal-container"] + ` ${isMax && styles["modal-container-max"]}`
}
>
<div className={styles["modal-header"]}>
<div className={styles["modal-title"]}>{props.title}</div>
<div className={styles["modal-close-btn"]} onClick={props.onClose}>
<CloseIcon />
<div className={styles["modal-header-actions"]}>
<div
className={styles["modal-header-action"]}
onClick={() => setMax(!isMax)}
>
{isMax ? <MinIcon /> : <MaxIcon />}
</div>
<div
className={styles["modal-header-action"]}
onClick={props.onClose}
>
<CloseIcon />
</div>
</div>
</div>
@@ -262,3 +289,197 @@ export function Select(
</div>
);
}
export function showConfirm(content: any) {
const div = document.createElement("div");
div.className = "modal-mask";
document.body.appendChild(div);
const root = createRoot(div);
const closeModal = () => {
root.unmount();
div.remove();
};
return new Promise<boolean>((resolve) => {
root.render(
<Modal
title={Locale.UI.Confirm}
actions={[
<IconButton
key="cancel"
text={Locale.UI.Cancel}
onClick={() => {
resolve(false);
closeModal();
}}
icon={<CancelIcon />}
tabIndex={0}
bordered
shadow
></IconButton>,
<IconButton
key="confirm"
text={Locale.UI.Confirm}
type="primary"
onClick={() => {
resolve(true);
closeModal();
}}
icon={<ConfirmIcon />}
tabIndex={0}
autoFocus
bordered
shadow
></IconButton>,
]}
onClose={closeModal}
>
{content}
</Modal>,
);
});
}
function PromptInput(props: {
value: string;
onChange: (value: string) => void;
rows?: number;
}) {
const [input, setInput] = useState(props.value);
const onInput = (value: string) => {
props.onChange(value);
setInput(value);
};
return (
<textarea
className={styles["modal-input"]}
autoFocus
value={input}
onInput={(e) => onInput(e.currentTarget.value)}
rows={props.rows ?? 3}
></textarea>
);
}
export function showPrompt(content: any, value = "", rows = 3) {
const div = document.createElement("div");
div.className = "modal-mask";
document.body.appendChild(div);
const root = createRoot(div);
const closeModal = () => {
root.unmount();
div.remove();
};
return new Promise<string>((resolve) => {
let userInput = value;
root.render(
<Modal
title={content}
actions={[
<IconButton
key="cancel"
text={Locale.UI.Cancel}
onClick={() => {
closeModal();
}}
icon={<CancelIcon />}
bordered
shadow
tabIndex={0}
></IconButton>,
<IconButton
key="confirm"
text={Locale.UI.Confirm}
type="primary"
onClick={() => {
resolve(userInput);
closeModal();
}}
icon={<ConfirmIcon />}
bordered
shadow
tabIndex={0}
></IconButton>,
]}
onClose={closeModal}
>
<PromptInput
onChange={(val) => (userInput = val)}
value={value}
rows={rows}
></PromptInput>
</Modal>,
);
});
}
export function showImageModal(img: string) {
showModal({
title: Locale.Export.Image.Modal,
children: (
<div>
<img
src={img}
alt="preview"
style={{
maxWidth: "100%",
}}
></img>
</div>
),
});
}
export function Selector<T>(props: {
items: Array<{
title: string;
subTitle?: string;
value: T;
}>;
defaultSelectedValue?: T;
onSelection?: (selection: T[]) => void;
onClose?: () => void;
multiple?: boolean;
}) {
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;
return (
<ListItem
className={styles["selector-item"]}
key={i}
title={item.title}
subTitle={item.subTitle}
onClick={() => {
props.onSelection?.([item.value]);
props.onClose?.();
}}
>
{selected ? (
<div
style={{
height: 10,
width: 10,
backgroundColor: "var(--primary)",
borderRadius: 10,
}}
></div>
) : (
<></>
)}
</ListItem>
);
})}
</List>
</div>
</div>
);
}

View File

@@ -1,15 +1,4 @@
const COMMIT_ID: string = (() => {
try {
const childProcess = require("child_process");
return childProcess
.execSync('git log -1 --format="%at000" --date=unix')
.toString()
.trim();
} catch (e) {
console.error("[Build Config] No git or not from git repo.");
return "unknown";
}
})();
import tauriConfig from "../../src-tauri/tauri.conf.json";
export const getBuildConfig = () => {
if (typeof process === "undefined") {
@@ -18,7 +7,38 @@ export const getBuildConfig = () => {
);
}
const buildMode = process.env.BUILD_MODE ?? "standalone";
const isApp = !!process.env.BUILD_APP;
const version = "v" + tauriConfig.package.version;
const commitInfo = (() => {
try {
const childProcess = require("child_process");
const commitDate: string = childProcess
.execSync('git log -1 --format="%at000" --date=unix')
.toString()
.trim();
const commitHash: string = childProcess
.execSync('git log --pretty=format:"%H" -n 1')
.toString()
.trim();
return { commitDate, commitHash };
} catch (e) {
console.error("[Build Config] No git or not from git repo.");
return {
commitDate: "unknown",
commitHash: "unknown",
};
}
})();
return {
commitId: COMMIT_ID,
version,
...commitInfo,
buildMode,
isApp,
};
};
export type BuildConfig = ReturnType<typeof getBuildConfig>;

27
app/config/client.ts Normal file
View File

@@ -0,0 +1,27 @@
import { BuildConfig, getBuildConfig } from "./build";
export function getClientConfig() {
if (typeof document !== "undefined") {
// client side
return JSON.parse(queryMeta("config")) as BuildConfig;
}
if (typeof process !== "undefined") {
// server side
return getBuildConfig();
}
}
function queryMeta(key: string, defaultValue?: string): string {
let ret: string;
if (document) {
const meta = document.head.querySelector(
`meta[name='${key}']`,
) as HTMLMetaElement;
ret = meta?.content ?? "";
} else {
ret = defaultValue ?? "";
}
return ret;
}

View File

@@ -1,14 +1,31 @@
import md5 from "spark-md5";
import { DEFAULT_MODELS } from "../constant";
declare global {
namespace NodeJS {
interface ProcessEnv {
PROXY_URL?: string; // docker only
OPENAI_API_KEY?: string;
CODE?: string;
PROXY_URL?: string;
BASE_URL?: string;
OPENAI_ORG_ID?: string; // openai only
VERCEL?: string;
BUILD_MODE?: "standalone" | "export";
BUILD_APP?: string; // is building desktop app
HIDE_USER_API_KEY?: string; // disable user's api key input
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
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
// azure only
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
AZURE_API_KEY?: string;
AZURE_API_VERSION?: string;
}
}
}
@@ -33,14 +50,39 @@ export const getServerSideConfig = () => {
);
}
const disableGPT4 = !!process.env.DISABLE_GPT4;
let customModels = process.env.CUSTOM_MODELS ?? "";
if (disableGPT4) {
if (customModels) customModels += ",";
customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4"))
.map((m) => "-" + m.name)
.join(",");
}
const isAzure = !!process.env.AZURE_URL;
return {
baseUrl: process.env.BASE_URL,
apiKey: process.env.OPENAI_API_KEY,
openaiOrgId: process.env.OPENAI_ORG_ID,
isAzure,
azureUrl: process.env.AZURE_URL,
azureApiKey: process.env.AZURE_API_KEY,
azureApiVersion: process.env.AZURE_API_VERSION,
needCode: ACCESS_CODES.size > 0,
code: process.env.CODE,
codes: ACCESS_CODES,
needCode: ACCESS_CODES.size > 0,
proxyUrl: process.env.PROXY_URL,
isVercel: !!process.env.VERCEL,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
enableGPT4: !process.env.DISABLE_GPT4,
disableGPT4,
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
disableFastLink: !!process.env.DISABLE_FAST_LINK,
customModels,
};
};

View File

@@ -3,20 +3,32 @@ export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
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`;
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
export const DEFAULT_CORS_HOST = "https://ab.nextweb.fun";
export const DEFAULT_API_HOST = `${DEFAULT_CORS_HOST}/api/proxy`;
export const OPENAI_BASE_URL = "https://api.openai.com";
export enum Path {
Home = "/",
Chat = "/chat",
Settings = "/settings",
NewChat = "/new-chat",
Masks = "/masks",
Auth = "/auth",
}
export enum ApiPath {
Cors = "/api/cors",
OpenAI = "/api/openai",
}
export enum SlotID {
AppBody = "app-body",
CustomModel = "custom-model",
}
export enum FileName {
@@ -31,14 +43,115 @@ export enum StoreKey {
Mask = "mask-store",
Prompt = "prompt-store",
Update = "chat-update",
Sync = "sync",
}
export const DEFAULT_SIDEBAR_WIDTH = 300;
export const MAX_SIDEBAR_WIDTH = 500;
export const MIN_SIDEBAR_WIDTH = 230;
export const NARROW_SIDEBAR_WIDTH = 100;
export const ACCESS_CODE_PREFIX = "ak-";
export const ACCESS_CODE_PREFIX = "nk-";
export const LAST_INPUT_KEY = "last-input";
export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
export const STORAGE_KEY = "chatgpt-next-web";
export const REQUEST_TIMEOUT_MS = 60000;
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
export enum ServiceProvider {
OpenAI = "OpenAI",
Azure = "Azure",
}
export const OpenaiPath = {
ChatPath: "v1/chat/completions",
UsagePath: "dashboard/billing/usage",
SubsPath: "dashboard/billing/subscription",
ListModelPath: "v1/models",
};
export const Azure = {
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
};
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
export const DEFAULT_SYSTEM_TEMPLATE = `
You are ChatGPT, a large language model trained by OpenAI.
Knowledge cutoff: {{cutoff}}
Current model: {{model}}
Current time: {{time}}
`;
export const SUMMARIZE_MODEL = "gpt-3.5-turbo";
export const KnowledgeCutOffDate: Record<string, string> = {
default: "2021-09",
"gpt-4-1106-preview": "2023-04",
"gpt-4-vision-preview": "2023-04",
};
export const DEFAULT_MODELS = [
{
name: "gpt-4",
available: true,
},
{
name: "gpt-4-0314",
available: true,
},
{
name: "gpt-4-0613",
available: true,
},
{
name: "gpt-4-32k",
available: true,
},
{
name: "gpt-4-32k-0314",
available: true,
},
{
name: "gpt-4-32k-0613",
available: true,
},
{
name: "gpt-4-1106-preview",
available: true,
},
{
name: "gpt-4-vision-preview",
available: true,
},
{
name: "gpt-3.5-turbo",
available: true,
},
{
name: "gpt-3.5-turbo-0301",
available: true,
},
{
name: "gpt-3.5-turbo-0613",
available: true,
},
{
name: "gpt-3.5-turbo-1106",
available: true,
},
{
name: "gpt-3.5-turbo-16k",
available: true,
},
{
name: "gpt-3.5-turbo-16k-0613",
available: true,
},
] as const;
export const CHAT_PAGE_SIZE = 15;
export const MAX_RENDER_MSG_COUNT = 45;

18
app/global.d.ts vendored
View File

@@ -9,3 +9,21 @@ declare module "*.scss" {
}
declare module "*.svg";
declare interface Window {
__TAURI__?: {
writeText(text: string): Promise<void>;
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
dialog: {
save(options?: Record<string, unknown>): Promise<string | null>;
};
fs: {
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
};
notification:{
requestPermission(): Promise<Permission>;
isPermissionGranted(): Promise<boolean>;
sendNotification(options: string | Options): void;
};
};
}

View File

@@ -1,23 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(1.3333333333333333 1.3333333333333333) rotate(0 6.666666666666666 6.666666666666666)"
d="M13.33,6.67C13.33,2.98 10.35,0 6.67,0C2.98,0 0,2.98 0,6.67C0,10.35 2.98,13.33 6.67,13.33C10.35,13.33 13.33,10.35 13.33,6.67Z " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(8 5.333333333333333) rotate(0 0 2.6666666666666665)" d="M0,0L0,5.33 " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(5.333333333333333 8) rotate(0 2.6666666666666665 0)" d="M0,0L5.33,0 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M13.33,6.67C13.33,2.98 10.35,0 6.67,0C2.98,0 0,2.98 0,6.67C0,10.35 2.98,13.33 6.67,13.33C10.35,13.33 13.33,10.35 13.33,6.67Z" transform="translate(1.3333333333333333 1.3333333333333333) rotate(0 6.666666666666666 6.666666666666666)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L0,5.33" transform="translate(8 5.333333333333333) rotate(0 0 2.6666666666666665)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L5.33,0" transform="translate(5.333333333333333 8) rotate(0 2.6666666666666665 0)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,28 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30"
height="30" viewBox="0 0 30 30" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="29.999999999999996" height="29.999999999999996" />
<rect id="path_1" x="0" y="0" width="20.45454545454545" height="20.45454545454545" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 14.999999999999998 14.999999999999998)">
<rect fill="#E7F8FF" opacity="1"
transform="translate(0 0) rotate(0 14.999999999999998 14.999999999999998)" x="0" y="0"
width="29.999999999999996" height="29.999999999999996" rx="10" />
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<g opacity="1"
transform="translate(4.772727272727272 4.772727272727273) rotate(0 10.227272727272725 10.227272727272725)">
<mask id="bg-mask-1" fill="white">
<use xlink:href="#path_1"></use>
</mask>
<g mask="url(#bg-mask-1)">
<path id="分组 1" fill-rule="evenodd" style="fill:#333333"
transform="translate(0 0) rotate(0 10.227272727272725 10.227272727272725)" opacity="1"
d="M19.11 8.37L19.11 8.37C19.28 7.85 19.37 7.31 19.37 6.76C19.37 5.86 19.13 4.97 18.66 4.19C17.73 2.59 16 1.6 14.13 1.6C13.76 1.6 13.4 1.64 13.04 1.71C12.06 0.62 10.65 0 9.17 0L9.14 0L9.13 0C6.86 0 4.86 1.44 4.16 3.57C2.7 3.86 1.44 4.76 0.71 6.04C0.24 6.83 0 7.72 0 8.63C0 9.9 0.48 11.14 1.35 12.08C1.17 12.6 1.08 13.15 1.08 13.69C1.08 14.6 1.33 15.49 1.79 16.27C2.92 18.21 5.2 19.21 7.42 18.74C8.4 19.83 9.8 20.45 11.28 20.45L11.31 20.45L11.33 20.45C13.59 20.45 15.6 19.01 16.3 16.88C17.76 16.59 19.01 15.69 19.75 14.41C20.21 13.63 20.45 12.74 20.45 11.83C20.45 10.55 19.97 9.32 19.11 8.37Z M8.94734 18.1579C8.90734 18.1879 8.86734 18.2079 8.82734 18.2279C9.52734 18.8079 10.3973 19.1179 11.3073 19.1179L11.3173 19.1179C13.4573 19.1179 15.1973 17.3979 15.1973 15.2879L15.1973 10.5279C15.1973 10.5079 15.1773 10.4879 15.1573 10.4779L13.4173 9.48792L13.4173 15.2379C13.4173 15.4679 13.2873 15.6879 13.0773 15.8079L8.94734 18.1579Z M8.27654 17.0048L12.4465 14.6248C12.4665 14.6148 12.4765 14.5948 12.4765 14.5748L12.4765 14.5748L12.4765 12.5848L7.43654 15.4548C7.22654 15.5748 6.96654 15.5748 6.75654 15.4548L2.62654 13.1048C2.58654 13.0848 2.53654 13.0448 2.50654 13.0348C2.46654 13.2448 2.44654 13.4648 2.44654 13.6848C2.44654 14.3548 2.62654 15.0148 2.96654 15.6048L2.96654 15.5948C3.66654 16.7848 4.94654 17.5148 6.33654 17.5148C7.01654 17.5148 7.68654 17.3348 8.27654 17.0048Z M3.90324 5.16818C3.90324 5.12818 3.90324 5.06818 3.90324 5.02818C3.05324 5.33818 2.33324 5.92818 1.88324 6.70818L1.88324 6.70818C1.54324 7.28818 1.36324 7.94818 1.36324 8.61818C1.36324 9.98818 2.10324 11.2582 3.30324 11.9482L7.47324 14.3182C7.49324 14.3282 7.51324 14.3282 7.53324 14.3182L9.28324 13.3182L4.24324 10.4482C4.03324 10.3382 3.90324 10.1182 3.90324 9.87818L3.90324 9.87818L3.90324 5.16818Z M17.1561 8.50521L12.9761 6.1252C12.9561 6.1252 12.9361 6.1252 12.9161 6.1352L11.1761 7.1252L16.2161 9.9952C16.4261 10.1152 16.5561 10.3352 16.5561 10.5752C16.5561 10.5752 16.5561 10.5752 16.5561 10.5752L16.5561 15.4252C18.0761 14.8652 19.0961 13.4352 19.0961 11.8252C19.0961 10.4552 18.3561 9.1952 17.1561 8.50521Z M8.01418 5.82927C7.99418 5.83927 7.98418 5.85927 7.98418 5.87927L7.98418 5.87927L7.98418 7.86927L13.0242 4.99927C13.1242 4.93927 13.2442 4.90927 13.3642 4.90927C13.4842 4.90927 13.5942 4.93927 13.7042 4.99927L17.8342 7.34927C17.8742 7.36927 17.9142 7.39927 17.9542 7.41927L17.9542 7.41927C17.9842 7.20927 18.0042 6.98927 18.0042 6.76927C18.0042 4.65927 16.2642 2.93927 14.1242 2.93927C13.4442 2.93927 12.7742 3.11927 12.1842 3.44927L8.01418 5.82927Z M9.14676 1.33731C6.99676 1.33731 5.25676 3.05731 5.25676 5.16731L5.25676 9.92731C5.25676 9.94731 5.27676 9.95731 5.28676 9.96731L7.03676 10.9673L7.03676 5.22731L7.03676 5.21731C7.03676 4.98731 7.16676 4.76731 7.37676 4.64731L11.5068 2.29731C11.5468 2.26731 11.5968 2.23731 11.6268 2.22731C10.9268 1.64731 10.0468 1.33731 9.14676 1.33731Z M7.98345 11.5093L10.2235 12.7793L12.4735 11.5093L12.4735 8.9493L10.2235 7.6693L7.98345 8.9493L7.98345 11.5093Z " />
</g>
</g>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30" height="30" fill="none" viewBox="0 0 30 30"><defs><rect id="path_0" width="30" height="30" x="0" y="0"/><rect id="path_1" width="20.455" height="20.455" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 14.999999999999998 14.999999999999998)"><rect width="30" height="30" x="0" y="0" fill="#E7F8FF" opacity="1" rx="10" transform="translate(0 0) rotate(0 14.999999999999998 14.999999999999998)"/><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><g opacity="1" transform="translate(4.772727272727272 4.772727272727273) rotate(0 10.227272727272725 10.227272727272725)"><mask id="bg-mask-1" fill="#fff"><use xlink:href="#path_1"/></mask><g mask="url(#bg-mask-1)"><path id="分组 1" fill-rule="evenodd" style="fill:#333" d="M19.11 8.37L19.11 8.37C19.28 7.85 19.37 7.31 19.37 6.76C19.37 5.86 19.13 4.97 18.66 4.19C17.73 2.59 16 1.6 14.13 1.6C13.76 1.6 13.4 1.64 13.04 1.71C12.06 0.62 10.65 0 9.17 0L9.14 0L9.13 0C6.86 0 4.86 1.44 4.16 3.57C2.7 3.86 1.44 4.76 0.71 6.04C0.24 6.83 0 7.72 0 8.63C0 9.9 0.48 11.14 1.35 12.08C1.17 12.6 1.08 13.15 1.08 13.69C1.08 14.6 1.33 15.49 1.79 16.27C2.92 18.21 5.2 19.21 7.42 18.74C8.4 19.83 9.8 20.45 11.28 20.45L11.31 20.45L11.33 20.45C13.59 20.45 15.6 19.01 16.3 16.88C17.76 16.59 19.01 15.69 19.75 14.41C20.21 13.63 20.45 12.74 20.45 11.83C20.45 10.55 19.97 9.32 19.11 8.37Z M8.94734 18.1579C8.90734 18.1879 8.86734 18.2079 8.82734 18.2279C9.52734 18.8079 10.3973 19.1179 11.3073 19.1179L11.3173 19.1179C13.4573 19.1179 15.1973 17.3979 15.1973 15.2879L15.1973 10.5279C15.1973 10.5079 15.1773 10.4879 15.1573 10.4779L13.4173 9.48792L13.4173 15.2379C13.4173 15.4679 13.2873 15.6879 13.0773 15.8079L8.94734 18.1579Z M8.27654 17.0048L12.4465 14.6248C12.4665 14.6148 12.4765 14.5948 12.4765 14.5748L12.4765 14.5748L12.4765 12.5848L7.43654 15.4548C7.22654 15.5748 6.96654 15.5748 6.75654 15.4548L2.62654 13.1048C2.58654 13.0848 2.53654 13.0448 2.50654 13.0348C2.46654 13.2448 2.44654 13.4648 2.44654 13.6848C2.44654 14.3548 2.62654 15.0148 2.96654 15.6048L2.96654 15.5948C3.66654 16.7848 4.94654 17.5148 6.33654 17.5148C7.01654 17.5148 7.68654 17.3348 8.27654 17.0048Z M3.90324 5.16818C3.90324 5.12818 3.90324 5.06818 3.90324 5.02818C3.05324 5.33818 2.33324 5.92818 1.88324 6.70818L1.88324 6.70818C1.54324 7.28818 1.36324 7.94818 1.36324 8.61818C1.36324 9.98818 2.10324 11.2582 3.30324 11.9482L7.47324 14.3182C7.49324 14.3282 7.51324 14.3282 7.53324 14.3182L9.28324 13.3182L4.24324 10.4482C4.03324 10.3382 3.90324 10.1182 3.90324 9.87818L3.90324 9.87818L3.90324 5.16818Z M17.1561 8.50521L12.9761 6.1252C12.9561 6.1252 12.9361 6.1252 12.9161 6.1352L11.1761 7.1252L16.2161 9.9952C16.4261 10.1152 16.5561 10.3352 16.5561 10.5752C16.5561 10.5752 16.5561 10.5752 16.5561 10.5752L16.5561 15.4252C18.0761 14.8652 19.0961 13.4352 19.0961 11.8252C19.0961 10.4552 18.3561 9.1952 17.1561 8.50521Z M8.01418 5.82927C7.99418 5.83927 7.98418 5.85927 7.98418 5.87927L7.98418 5.87927L7.98418 7.86927L13.0242 4.99927C13.1242 4.93927 13.2442 4.90927 13.3642 4.90927C13.4842 4.90927 13.5942 4.93927 13.7042 4.99927L17.8342 7.34927C17.8742 7.36927 17.9142 7.39927 17.9542 7.41927L17.9542 7.41927C17.9842 7.20927 18.0042 6.98927 18.0042 6.76927C18.0042 4.65927 16.2642 2.93927 14.1242 2.93927C13.4442 2.93927 12.7742 3.11927 12.1842 3.44927L8.01418 5.82927Z M9.14676 1.33731C6.99676 1.33731 5.25676 3.05731 5.25676 5.16731L5.25676 9.92731C5.25676 9.94731 5.27676 9.95731 5.28676 9.96731L7.03676 10.9673L7.03676 5.22731L7.03676 5.21731C7.03676 4.98731 7.16676 4.76731 7.37676 4.64731L11.5068 2.29731C11.5468 2.26731 11.5968 2.23731 11.6268 2.22731C10.9268 1.64731 10.0468 1.33731 9.14676 1.33731Z M7.98345 11.5093L10.2235 12.7793L12.4735 11.5093L12.4735 8.9493L10.2235 7.6693L7.98345 8.9493L7.98345 11.5093Z" opacity="1" transform="translate(0 0) rotate(0 10.227272727272725 10.227272727272725)"/></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
app/icons/bot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 4) rotate(0 4 2)" d="M8,0L4,4L0,0 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 8) rotate(0 4 2)" d="M8,0L4,4L0,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M8,0L4,4L0,0" transform="translate(4 4) rotate(0 4 2)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M8,0L4,4L0,0" transform="translate(4 8) rotate(0 4 2)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 706 B

View File

@@ -1,25 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(1.3333323286384866 1.3334133333333331) rotate(0 6.66666716901409 6.66666)"
d="M5.01,13.33C4.69,12.27 4.19,11.47 3.53,10.95C2.55,10.17 0.97,10.65 0.39,9.84C-0.19,9.04 0.8,7.55 1.15,6.67C1.49,5.79 -0.18,5.48 0.02,5.23C0.15,5.07 0.99,4.59 2.55,3.79C3,1.26 4.63,0 7.47,0C11.71,0 13.33,3.6 13.33,5.89C13.33,8.18 11.37,10.65 8.58,11.18C8.33,11.55 8.69,12.26 9.66,13.33 " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(6.374029736345404 3.9567867125879106) rotate(0 2.8215982497276006 2.4327734241007346)"
d="M2.1,3.33C1.91,4.42 2.14,4.93 2.79,4.86C3.44,4.79 3.84,4.52 3.97,4.05C4.99,4.33 5.54,4.09 5.63,3.33C5.75,2.18 5.13,1.26 4.88,1.26C4.63,1.26 3.97,1.23 3.97,0.88C3.97,0.52 3.2,0.33 2.5,0.33C1.81,0.33 2.23,-0.14 1.27,0.04C0.64,0.17 0.26,0.44 0.13,0.88C-0.09,1.72 -0.03,2.31 0.32,2.66C0.67,3 1.26,3.22 2.1,3.33Z " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(8.193033333333332 8.500066666666665) rotate(0 0.9868499999999998 1.1846833333333333)"
d="M1.97,0C1.63,0.21 1.17,0.56 0.97,0.83C0.48,1.52 0.09,1.93 0,2.37 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M5.01,13.33C4.69,12.27 4.19,11.47 3.53,10.95C2.55,10.17 0.97,10.65 0.39,9.84C-0.19,9.04 0.8,7.55 1.15,6.67C1.49,5.79 -0.18,5.48 0.02,5.23C0.15,5.07 0.99,4.59 2.55,3.79C3,1.26 4.63,0 7.47,0C11.71,0 13.33,3.6 13.33,5.89C13.33,8.18 11.37,10.65 8.58,11.18C8.33,11.55 8.69,12.26 9.66,13.33" transform="translate(1.3333323286384866 1.3334133333333331) rotate(0 6.66666716901409 6.66666)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M2.1,3.33C1.91,4.42 2.14,4.93 2.79,4.86C3.44,4.79 3.84,4.52 3.97,4.05C4.99,4.33 5.54,4.09 5.63,3.33C5.75,2.18 5.13,1.26 4.88,1.26C4.63,1.26 3.97,1.23 3.97,0.88C3.97,0.52 3.2,0.33 2.5,0.33C1.81,0.33 2.23,-0.14 1.27,0.04C0.64,0.17 0.26,0.44 0.13,0.88C-0.09,1.72 -0.03,2.31 0.32,2.66C0.67,3 1.26,3.22 2.1,3.33Z" transform="translate(6.374029736345404 3.9567867125879106) rotate(0 2.8215982497276006 2.4327734241007346)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M1.97,0C1.63,0.21 1.17,0.56 0.97,0.83C0.48,1.52 0.09,1.93 0,2.37" transform="translate(8.193033333333332 8.500066666666665) rotate(0 0.9868499999999998 1.1846833333333333)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><g opacity="1" transform="translate(1.0001220703125 2) rotate(0)"><path id="路径 1" style="fill:#333333; opacity:1;" d="M13.275,-0.27515c0.261,0.26101 0.3915,0.57606 0.3915,0.94515v10.66c0,0.36907 -0.1305,0.68413 -0.3915,0.9452c-0.261,0.261 -0.57603,0.3915 -0.9451,0.3915h-10.66002c-0.36909,0 -0.68415,-0.1305 -0.94516,-0.3915c-0.26101,-0.26107 -0.39151,-0.57613 -0.39151,-0.9452v-10.66c0,-0.3691 0.1305,-0.68415 0.39151,-0.94515c0.26101,-0.26101 0.57606,-0.39151 0.94516,-0.39151h10.66002c0.36907,0 0.6841,0.1305 0.9451,0.39151zM1.66655,11.33c0,0.0022 0.00111,0.0033 0.00333,0.0033h10.66002c0.0022,0 0.0033,-0.0011 0.0033,-0.0033v-10.66c0,-0.00222 -0.0011,-0.00333 -0.0033,-0.00333l-10.66002,0c-0.00222,0 -0.00333,0.00111 -0.00333,0.00333z"></path><path id="路径 2" style="fill:#333333; opacity:1;" d="M9.76327,7.50715c-0.02999,0.02563 -0.06201,0.04842 -0.09604,0.06837c-0.03403,0.01995 -0.06956,0.03674 -0.10658,0.05039c-0.03702,0.01364 -0.07495,0.02391 -0.11379,0.03082c-0.03885,0.00691 -0.07799,0.01035 -0.11744,0.0103c-0.03945,-0.00004 -0.07859,-0.00356 -0.11742,-0.01055c-0.03883,-0.00699 -0.07674,-0.01734 -0.11373,-0.03106c-0.03699,-0.01372 -0.07248,-0.03059 -0.10647,-0.05061c-0.03399,-0.02002 -0.06596,-0.04288 -0.0959,-0.06858l-1.89578,-1.62728l-1.89578,1.62728c-0.02993,0.0257 -0.0619,0.04856 -0.09589,0.06858c-0.03399,0.02002 -0.06949,0.03689 -0.10648,0.05061c-0.03699,0.01372 -0.07489,0.02407 -0.11372,0.03106c-0.03883,0.00699 -0.07797,0.01051 -0.11742,0.01055c-0.03945,0.00005 -0.0786,-0.00339 -0.11744,-0.0103c-0.03885,-0.00691 -0.07678,-0.01718 -0.11379,-0.03082c-0.03702,-0.01365 -0.07255,-0.03044 -0.10658,-0.05039c-0.03404,-0.01995 -0.06605,-0.04274 -0.09604,-0.06837l-1.90593,-1.629l-1.89671,1.62808c-0.06708,0.05758 -0.14263,0.10013 -0.22664,0.12766c-0.08401,0.02753 -0.17009,0.03793 -0.25824,0.03121c-0.08815,-0.00671 -0.17166,-0.03004 -0.25053,-0.06998c-0.07887,-0.03994 -0.14709,-0.09345 -0.20467,-0.16054c-0.02851,-0.03321 -0.05351,-0.06889 -0.07499,-0.10703c-0.02148,-0.03814 -0.03904,-0.07801 -0.05267,-0.11961c-0.01363,-0.04159 -0.02307,-0.08412 -0.02832,-0.12758c-0.00525,-0.04346 -0.00622,-0.08701 -0.00289,-0.13066c0.00333,-0.04365 0.01088,-0.08655 0.02266,-0.12871c0.01178,-0.04216 0.02755,-0.08277 0.04733,-0.12182c0.01978,-0.03905 0.04317,-0.07579 0.07019,-0.11024c0.02701,-0.03444 0.05713,-0.06592 0.09035,-0.09443l2.32999,-2c0.02994,-0.02569 0.06191,-0.04855 0.0959,-0.06857c0.03399,-0.02003 0.06948,-0.0369 0.10647,-0.05062c0.03699,-0.01372 0.0749,-0.02407 0.11373,-0.03106c0.03883,-0.00699 0.07797,-0.01051 0.11742,-0.01055c0.03945,-0.00004 0.0786,0.00339 0.11744,0.0103c0.03884,0.00691 0.07677,0.01718 0.11379,0.03082c0.03702,0.01365 0.07255,0.03044 0.10658,0.05039c0.03404,0.01995 0.06605,0.04274 0.09604,0.06837l1.90592,1.629l1.89671,-1.62808c0.02998,-0.02573 0.062,-0.04862 0.09605,-0.06866c0.03405,-0.02005 0.0696,-0.03693 0.10665,-0.05065c0.03705,-0.01372 0.07503,-0.02407 0.11392,-0.03104c0.03889,-0.00697 0.07809,-0.01045 0.1176,-0.01045c0.03951,0 0.07872,0.00348 0.11761,0.01045c0.03889,0.00697 0.07686,0.01732 0.11391,0.03104c0.03705,0.01372 0.0726,0.0306 0.10665,0.05065c0.03405,0.02004 0.06607,0.04293 0.09605,0.06866l1.89671,1.62808l1.90595,-1.629c0.03,-0.02563 0.062,-0.04842 0.096,-0.06837c0.03407,-0.01995 0.0696,-0.03674 0.1066,-0.05038c0.037,-0.01365 0.07493,-0.02392 0.1138,-0.03083c0.03887,-0.00691 0.078,-0.01034 0.1174,-0.0103c0.03947,0.00004 0.0786,0.00356 0.1174,0.01055c0.03887,0.00699 0.0768,0.01734 0.1138,0.03106c0.037,0.01372 0.07247,0.03059 0.1064,0.05062c0.034,0.02002 0.06597,0.04288 0.0959,0.06857l2.33,2c0.06713,0.05758 0.12067,0.12581 0.1606,0.20468c0.03993,0.07887 0.06327,0.16237 0.07,0.25052c0.00667,0.08815 -0.00377,0.17424 -0.0313,0.25825c-0.02747,0.08401 -0.07,0.15955 -0.1276,0.22663c-0.02853,0.03322 -0.06,0.06334 -0.0944,0.09035c-0.03447,0.02701 -0.07123,0.05041 -0.1103,0.07019c-0.03907,0.01977 -0.07967,0.03555 -0.1218,0.04733c-0.04213,0.01177 -0.08503,0.01932 -0.1287,0.02265c-0.04367,0.00333 -0.08723,0.00236 -0.1307,-0.00289c-0.04347,-0.00525 -0.086,-0.01469 -0.1276,-0.02832c-0.0416,-0.01363 -0.08147,-0.03118 -0.1196,-0.05267c-0.03813,-0.02148 -0.0738,-0.04648 -0.107,-0.07499l-1.8967,-1.62808z"></path></g><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ></g></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="fill:#333333; opacity:1;" d="M13.9967,8.00337c0,-0.81625 -0.1569,-1.59615 -0.4707,-2.3397c-0.30307,-0.71824 -0.73117,-1.3542 -1.2843,-1.90789c-0.55287,-0.55348 -1.18783,-0.98185 -1.9049,-1.28512c-0.74182,-0.31375 -1.51963,-0.47062 -2.33343,-0.47062c-0.81634,0 -1.59621,0.15693 -2.3396,0.47079c-0.71828,0.30325 -1.35419,0.73165 -1.90774,1.2852c-0.55355,0.55354 -0.98195,1.18945 -1.2852,1.90774c-0.31386,0.74339 -0.47079,1.52326 -0.47079,2.3396c0,0.8138 0.15687,1.59161 0.47062,2.33343c0.30327,0.71707 0.73164,1.35203 1.28512,1.9049c0.55369,0.55313 1.18965,0.98123 1.90789,1.2843c0.74355,0.3138 1.52345,0.4707 2.3397,0.4707c0.81371,0 1.59155,-0.15683 2.33353,-0.4705c0.717,-0.30313 1.35203,-0.7312 1.9051,-1.2842c0.553,-0.55307 0.98107,-1.1881 1.2842,-1.9051c0.31367,-0.74198 0.4705,-1.51982 0.4705,-2.33353zM15.33,8.00337c0,0.99387 -0.1919,1.94478 -0.5757,2.85273c-0.37067,0.87673 -0.89383,1.65297 -1.5695,2.3287c-0.67573,0.67567 -1.45197,1.19883 -2.3287,1.5695c-0.90795,0.3838 -1.85886,0.5757 -2.85273,0.5757c-0.99612,0 -1.94882,-0.19183 -2.8581,-0.5755c-0.8781,-0.3706 -1.65537,-0.89377 -2.33181,-1.5695c-0.67631,-0.6756 -1.19992,-1.45187 -1.57081,-2.3288c-0.38396,-0.90784 -0.57594,-1.85878 -0.57594,-2.85283c0,-0.99629 0.19192,-1.94903 0.57577,-2.8582c0.37081,-0.87829 0.89439,-1.6556 1.57074,-2.33195c0.67635,-0.67635 1.45367,-1.19993 2.33195,-1.57074c0.90917,-0.38385 1.86191,-0.57577 2.8582,-0.57577c0.99405,0 1.94499,0.19198 2.85283,0.57594c0.87693,0.37089 1.6532,0.8945 2.3288,1.57081c0.67573,0.67644 1.1989,1.45371 1.5695,2.33181c0.38367,0.90928 0.5755,1.86198 0.5755,2.8581z"></path><path id="路径 2" style="fill:#333333; opacity:1;" d="M5.4714,4.5286l6,6c0.03093,0.03093 0.05857,0.0646 0.0829,0.101c0.02433,0.0364 0.04487,0.07483 0.0616,0.1153c0.01673,0.0404 0.0294,0.08207 0.038,0.125c0.00853,0.04293 0.0128,0.0863 0.0128,0.1301c0,0.0438 -0.00427,0.08717 -0.0128,0.1301c-0.0086,0.04293 -0.02127,0.0846 -0.038,0.125c-0.01673,0.04047 -0.03727,0.0789 -0.0616,0.1153c-0.02433,0.0364 -0.05197,0.07007 -0.0829,0.101c-0.03093,0.03093 -0.0646,0.05857 -0.101,0.0829c-0.0364,0.02433 -0.07483,0.04487 -0.1153,0.0616c-0.0404,0.01673 -0.08207,0.0294 -0.125,0.038c-0.04293,0.00853 -0.0863,0.0128 -0.1301,0.0128c-0.0438,0 -0.08717,-0.00427 -0.1301,-0.0128c-0.04293,-0.0086 -0.0846,-0.02127 -0.125,-0.038c-0.04047,-0.01673 -0.0789,-0.03727 -0.1153,-0.0616c-0.0364,-0.02433 -0.07007,-0.05197 -0.101,-0.0829l-6,-6c-0.03095,-0.03095 -0.05859,-0.06463 -0.08291,-0.10102c-0.02432,-0.0364 -0.04486,-0.07482 -0.06161,-0.11526c-0.01675,-0.04044 -0.0294,-0.08213 -0.03794,-0.12506c-0.00854,-0.04293 -0.01281,-0.08629 -0.01281,-0.13006c0,-0.04377 0.00427,-0.08713 0.01281,-0.13006c0.00854,-0.04293 0.02119,-0.08462 0.03794,-0.12506c0.01675,-0.04045 0.03729,-0.07887 0.06161,-0.11526c0.02432,-0.0364 0.05196,-0.07007 0.08291,-0.10102c0.03095,-0.03095 0.06462,-0.05859 0.10102,-0.08291c0.03639,-0.02432 0.07481,-0.04486 0.11526,-0.06161c0.04044,-0.01675 0.08213,-0.0294 0.12506,-0.03794c0.04293,-0.00854 0.08629,-0.01281 0.13006,-0.01281c0.04377,0 0.08713,0.00427 0.13006,0.01281c0.04293,0.00854 0.08462,0.02119 0.12506,0.03794c0.04044,0.01675 0.07886,0.03729 0.11526,0.06161c0.03639,0.02432 0.07007,0.05196 0.10102,0.08291z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
app/icons/chatgpt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1
app/icons/cloud-fail.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use transform="translate(0 0) rotate(0)" xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="fill:#333333; opacity:1;" d="M4.00337,11.6633M4.00337,11.6633c-0.8391,0 -1.5514,-0.20763 -2.13691,-0.6229c-0.79984,-0.56727 -1.19975,-1.41519 -1.19975,-2.54377c0,-1.2521 0.52073,-2.20348 1.56218,-2.85415c0.68561,-0.42835 1.38711,-0.64252 2.10448,-0.64252v0.66667l-0.64163,-0.18098l0.64163,0.18098l-0.64163,-0.18098c0.65543,-2.32379 2.09264,-3.48569 4.31163,-3.48569c2.30635,0 3.74023,0.99523 4.30163,2.98569l-0.6416,0.18098l0.0729,-0.66267l-0.0729,0.66267l0.0729,-0.66267c2.3958,0.26354 3.5937,1.5411 3.5937,3.83267c0,2.21778 -1.10887,3.32667 -3.3266,3.32667c-0.0438,0 -0.08717,-0.00427 -0.1301,-0.0128c-0.04293,-0.00853 -0.0846,-0.0212 -0.125,-0.038c-0.04047,-0.01673 -0.0789,-0.03727 -0.1153,-0.0616c-0.0364,-0.02427 -0.07007,-0.0519 -0.101,-0.0829c-0.031,-0.03093 -0.05863,-0.0646 -0.0829,-0.101c-0.02433,-0.0364 -0.04487,-0.07483 -0.0616,-0.1153c-0.0168,-0.0404 -0.02947,-0.08207 -0.038,-0.125c-0.00853,-0.04293 -0.0128,-0.0863 -0.0128,-0.1301c0,-0.04373 0.00427,-0.08707 0.0128,-0.13c0.00853,-0.04293 0.0212,-0.08463 0.038,-0.1251c0.01673,-0.04047 0.03727,-0.0789 0.0616,-0.1153c0.02427,-0.0364 0.0519,-0.07007 0.0829,-0.101c0.03093,-0.03093 0.0646,-0.05857 0.101,-0.0829c0.0364,-0.02433 0.07483,-0.04487 0.1153,-0.0616c0.0404,-0.01673 0.08207,-0.02937 0.125,-0.0379c0.04293,-0.00853 0.0863,-0.0128 0.1301,-0.0128c1.32887,0 1.9933,-0.66446 1.9933,-1.99337c0,-1.4951 -0.80207,-2.33088 -2.4062,-2.50733c-0.27,-0.02971 -0.495,-0.22026 0,0c-0.27,-0.02971 -0.495,-0.22026 -0.5688,-0.4817c-0.37873,-1.34287 -1.38484,-2.01431 -3.01833,-2.01431c-1.54613,0 -2.55559,0.8381 -3.02836,2.51431c-0.08103,0.28728 -0.34314,0.48569 0,0c-0.08103,0.28728 -0.34314,0.48569 -0.64164,0.48569c-0.46252,0 -0.92852,0.14666 -1.39801,0.43998c-0.62355,0.38957 -0.93532,0.96402 -0.93532,1.72336c0,0.66927 0.21258,1.15467 0.63775,1.45621c0.35449,0.25144 0.80969,0.37716 1.36558,0.37716c0.04378,0 0.08713,0.00427 0.13006,0.0128c0.04293,0.00853 0.08462,0.02117 0.12507,0.0379c0.04044,0.01673 0.07886,0.03727 0.11525,0.0616c0.0364,0.02433 0.07008,0.05197 0.10103,0.0829c0.03095,0.03093 0.05859,0.0646 0.08291,0.101c0.02432,0.0364 0.04485,0.07483 0.0616,0.1153c0.01675,0.04047 0.0294,0.08217 0.03794,0.1251c0.00854,0.04293 0.01281,0.08627 0.01281,0.13c0,0.0438 -0.00427,0.08717 -0.01281,0.1301c-0.00854,0.04293 -0.02119,0.0846 -0.03794,0.125c-0.01675,0.04047 -0.03728,0.0789 -0.0616,0.1153c-0.02432,0.0364 -0.05196,0.07007 -0.08291,0.101c-0.03095,0.031 -0.06463,0.05863 -0.10103,0.0829c-0.03639,0.02433 -0.07481,0.04487 -0.11525,0.0616c-0.04045,0.0168 -0.08214,0.02947 -0.12507,0.038c-0.04293,0.00853 -0.08628,0.0128 -0.13006,0.0128z"></path><path id="路径 2" style="fill:#333333; opacity:1;" d="M6.42578,10.4903l2,1.66l-0.42578,0.513l-0.52012,-0.4171l2.67002,-3.32998c0.0274,-0.03415 0.05783,-0.06531 0.0913,-0.09346c0.03353,-0.02815 0.0695,-0.05277 0.1079,-0.07384c0.03833,-0.02107 0.07837,-0.0382 0.1201,-0.05138c0.04173,-0.01319 0.08437,-0.02217 0.1279,-0.02696c0.04353,-0.00479 0.0871,-0.00528 0.1307,-0.00149c0.0436,0.00379 0.0864,0.01181 0.1284,0.02404c0.04207,0.01223 0.0825,0.02844 0.1213,0.04863c0.03887,0.0202 0.07537,0.04399 0.1095,0.07137c0.0342,0.02738 0.06537,0.05783 0.0935,0.09135c0.02813,0.03352 0.05273,0.06946 0.0738,0.10783c0.02107,0.03837 0.0382,0.07843 0.0514,0.12017c0.0132,0.04174 0.0222,0.08437 0.027,0.12788c0.00473,0.04351 0.00523,0.08707 0.0015,0.13068c-0.0038,0.04361 -0.01183,0.08643 -0.0241,0.12846c-0.0122,0.04203 -0.0284,0.08247 -0.0486,0.1213c-0.0202,0.03884 -0.044,0.07534 -0.0714,0.10949l-2.66998,3.33001c-0.23308,0.2907 -0.65919,0.3339 -0.9459,0.0959l-2,-1.66c-0.03369,-0.02793 -0.06432,-0.0589 -0.0919,-0.0929c-0.02758,-0.034 -0.05158,-0.07037 -0.072,-0.1091c-0.02042,-0.03867 -0.03687,-0.079 -0.04934,-0.121c-0.01248,-0.04193 -0.02074,-0.0847 -0.02479,-0.1283c-0.00405,-0.0436 -0.0038,-0.08717 0.00073,-0.1307c0.00453,-0.04353 0.01326,-0.0862 0.0262,-0.128c0.01294,-0.0418 0.02983,-0.08197 0.05068,-0.1205c0.02085,-0.03847 0.04526,-0.07453 0.07321,-0.1082c0.02796,-0.03373 0.05893,-0.06437 0.09292,-0.0919c0.03399,-0.0276 0.07035,-0.0516 0.10907,-0.072c0.03872,-0.02047 0.07906,-0.03693 0.12102,-0.0494c0.04196,-0.01247 0.08473,-0.02073 0.12831,-0.0248c0.04359,-0.004 0.08715,-0.00373 0.13069,0.0008c0.04354,0.00453 0.08622,0.01327 0.12804,0.0262c0.04181,0.01293 0.08197,0.02983 0.12046,0.0507c0.03849,0.0208 0.07457,0.0452 0.10826,0.0732z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

1
app/icons/config.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="fill:#333333; opacity:1;" d="M5.99607,12.8916c-0.03633,0.02413 -0.07466,0.04453 -0.11499,0.0612c-0.04034,0.01667 -0.08191,0.02923 -0.12471,0.0377c-0.04281,0.00853 -0.08603,0.0128 -0.12967,0.0128c-0.04364,0 -0.08686,-0.00423 -0.12966,-0.0127c-0.04281,-0.00847 -0.08438,-0.02103 -0.12472,-0.0377c-0.04034,-0.01667 -0.07867,-0.03707 -0.115,-0.0612c-0.03633,-0.0242 -0.06997,-0.0517 -0.1009,-0.0825l-3.96,-3.93998c-0.03103,-0.03087 -0.05876,-0.06448 -0.08317,-0.10081c-0.02441,-0.03634 -0.04505,-0.07471 -0.0619,-0.1151c-0.01685,-0.0404 -0.0296,-0.08206 -0.03825,-0.12497c-0.00865,-0.04291 -0.01303,-0.08626 -0.01314,-0.13003c-0.00011,-0.04377 0.00405,-0.08714 0.01248,-0.13009c0.00843,-0.04295 0.02097,-0.08467 0.03762,-0.12516c0.01665,-0.04048 0.03709,-0.07895 0.06132,-0.11541c0.02423,-0.03646 0.05178,-0.0702 0.08265,-0.10123c0.03087,-0.03103 0.06448,-0.05876 0.10081,-0.08317c0.03634,-0.02441 0.07471,-0.04505 0.11511,-0.0619c0.04039,-0.01685 0.08205,-0.0296 0.12496,-0.03825c0.04291,-0.00865 0.08626,-0.01303 0.13003,-0.01314c0.04377,-0.00011 0.08714,0.00405 0.13009,0.01248c0.04295,0.00843 0.08467,0.02097 0.12516,0.03762c0.04048,0.01665 0.07895,0.03709 0.11541,0.06132c0.03646,0.02423 0.07021,0.05178 0.10124,0.08265l3.48968,3.47207l8.23978,-8.20196c0.031,-0.03088 0.06473,-0.05844 0.1012,-0.08268c0.03647,-0.02423 0.07493,-0.04468 0.1154,-0.06134c0.04047,-0.01666 0.0822,-0.02921 0.1252,-0.03765c0.04293,-0.00844 0.0863,-0.01261 0.1301,-0.01251c0.04373,0.0001 0.08707,0.00447 0.13,0.01311c0.04293,0.00864 0.0846,0.02138 0.125,0.03822c0.0404,0.01685 0.07877,0.03747 0.1151,0.06188c0.03633,0.0244 0.06993,0.05211 0.1008,0.08314c0.0624,0.06265 0.1104,0.13486 0.144,0.21661c0.03367,0.08175 0.0504,0.16683 0.0502,0.25524c-0.0002,0.08841 -0.0173,0.17341 -0.0513,0.25501c-0.03407,0.08159 -0.08243,0.15357 -0.1451,0.21594l-8.70996,8.66999c-0.03093,0.0308 -0.06455,0.0583 -0.10087,0.0825z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
app/icons/connection.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4.333333333333333 1.6666666666666665) rotate(0 5 5)" d="M0,2.48L0,0.94C0,0.42 0.42,0 0.94,0L9.06,0C9.58,0 10,0.42 10,0.94L10,9.06C10,9.58 9.58,10 9.06,10L7.51,10 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.6666666666666665 4.333333333333333) rotate(0 5 5)" d="M0.94,0C0.42,0 0,0.42 0,0.94L0,9.06C0,9.58 0.42,10 0.94,10L9.06,10C9.58,10 10,9.58 10,9.06L10,0.94C10,0.42 9.58,0 9.06,0L0.94,0Z " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,2.48L0,0.94C0,0.42 0.42,0 0.94,0L9.06,0C9.58,0 10,0.42 10,0.94L10,9.06C10,9.58 9.58,10 9.06,10L7.51,10" transform="translate(4.333333333333333 1.6666666666666665) rotate(0 5 5)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0.94,0C0.42,0 0,0.42 0,0.94L0,9.06C0,9.58 0.42,10 0.94,10L9.06,10C9.58,10 10,9.58 10,9.06L10,0.94C10,0.42 9.58,0 9.06,0L0.94,0Z" transform="translate(1.6666666666666665 4.333333333333333) rotate(0 5 5)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 981 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(-90 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 4) rotate(0 2 4)" d="M4,8L0,4L4,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(-90 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M4,8L0,4L4,0" transform="translate(6.333333333333333 4) rotate(0 2 4)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 575 B

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(2 2) rotate(0 6 6)" d="M1,12L11,12C11.55,12 12,11.55 12,11L12,1C12,0.45 11.55,0 11,0L1,0C0.45,0 0,0.45 0,1L0,11C0,11.55 0.45,12 1,12Z " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333333333333333 10.333333333333332) rotate(0 6.666666666666666 0.6666666666666666)" d="M0,0L3.67,0L4.33,1.33L9,1.33L9.67,0L13.33,0 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(14 8.666666666666666) rotate(0 0 1.6666666666666665)" d="M0,3.33L0,0 " /><path id="路径 4" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6 7.333333333333333) rotate(0 2 1)" d="M0,0L2,2L4,0 " /><path id="路径 5" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8 4) rotate(0 0 2.6666666666666665)" d="M0,5.33L0,0 " /><path id="路径 6" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(2 8.666666666666666) rotate(0 0 1.6666666666666665)" d="M0,3.33L0,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M1,12L11,12C11.55,12 12,11.55 12,11L12,1C12,0.45 11.55,0 11,0L1,0C0.45,0 0,0.45 0,1L0,11C0,11.55 0.45,12 1,12Z" transform="translate(2 2) rotate(0 6 6)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L3.67,0L4.33,1.33L9,1.33L9.67,0L13.33,0" transform="translate(1.3333333333333333 10.333333333333332) rotate(0 6.666666666666666 0.6666666666666666)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3.33L0,0" transform="translate(14 8.666666666666666) rotate(0 0 1.6666666666666665)"/><path id="路径 4" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L2,2L4,0" transform="translate(6 7.333333333333333) rotate(0 2 1)"/><path id="路径 5" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,5.33L0,0" transform="translate(8 4) rotate(0 0 2.6666666666666665)"/><path id="路径 6" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3.33L0,0" transform="translate(2 8.666666666666666) rotate(0 0 1.6666666666666665)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use transform="translate(0 0) rotate(0)" xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M6.33663,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.33,-0.6 -1.33,-1.34c0,-0.73 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.6 1.34,1.33zM4.99663,9.33c-0.73,0 -1.33,-0.59 -1.33,-1.33c0,-0.74 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM4.99663,14c-0.73,0 -1.33,-0.6 -1.33,-1.33c0,-0.74 0.6,-1.34 1.33,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path><path id="路径 2" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M12.3366,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.32997,-0.6 -1.32997,-1.34c0,-0.73 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.6 1.34,1.33zM10.9966,9.33c-0.73,0 -1.32997,-0.59 -1.32997,-1.33c0,-0.74 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM10.9966,14c-0.73,0 -1.32997,-0.6 -1.32997,-1.33c0,-0.74 0.59997,-1.34 1.32997,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.2400716519614834 2.3333321805983163) rotate(0 6.785117896431597 4.552683909700841)" d="M12.27,9.11C13.36,8.34 13.83,6.94 13.43,5.67C13.02,4.39 11.78,3.69 10.44,3.69L9.67,3.69C9.16,1.72 7.5,0.27 5.47,0.03C3.45,-0.2 1.5,0.84 0.56,2.64C-0.38,4.45 -0.11,6.64 1.23,8.17 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8 7.666666666666666) rotate(0 0.00140000000000029 3)" d="M0,6L0,0 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.8786 11.5454) rotate(0 2.1213333333333333 1.0606666666666662)" d="M4.24,0L2.12,2.12L0,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M12.27,9.11C13.36,8.34 13.83,6.94 13.43,5.67C13.02,4.39 11.78,3.69 10.44,3.69L9.67,3.69C9.16,1.72 7.5,0.27 5.47,0.03C3.45,-0.2 1.5,0.84 0.56,2.64C-0.38,4.45 -0.11,6.64 1.23,8.17" transform="translate(1.2400716519614834 2.3333321805983163) rotate(0 6.785117896431597 4.552683909700841)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,6L0,0" transform="translate(8 7.666666666666666) rotate(0 0.00140000000000029 3)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M4.24,0L2.12,2.12L0,0" transform="translate(5.8786 11.5454) rotate(0 2.1213333333333333 1.0606666666666662)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,29 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.6666666666666665 1.644694921083138) rotate(0 5.333333333333333 4.287969206125098)"
d="M7.11,8.51C7.92,8.35 8.64,8.06 9.21,7.64C10.17,6.91 10.67,5.79 10.67,4.69C10.67,3.91 10.37,3.19 9.86,2.58C9.58,2.24 10.41,-0.31 9.67,0.03C8.94,0.37 7.86,1.13 7.29,0.97C6.68,0.79 6.02,0.69 5.33,0.69C4.73,0.69 4.16,0.76 3.62,0.9C2.83,1.1 2.09,0.36 1.33,0.03C0.58,-0.29 0.99,2.34 0.77,2.62C0.28,3.22 0,3.93 0,4.69C0,5.79 0.6,6.91 1.56,7.64C2.21,8.12 3.01,8.42 3.91,8.58 " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(6.000666666666666 10.220633333333332) rotate(0 0.2896166666666667 2.058116666666666)"
d="M0.58,0C0.19,0.43 0,0.83 0,1.21C0,1.59 0,2.56 0,4.12 " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(9.781533333333332 10.158866666666666) rotate(0 0.2744333333333332 2.0890166666666663)"
d="M0,0C0.37,0.48 0.55,0.91 0.55,1.29C0.55,1.68 0.55,2.64 0.55,4.18 " />
<path id="路径 4"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 10.405166666666666) rotate(0 2.0004 1.050416666666667)"
d="M0,0C0.3,0.04 0.52,0.17 0.67,0.41C0.88,0.77 1.69,2.1 2.61,2.1C3.22,2.1 3.68,2.1 4,2.1 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M7.11,8.51C7.92,8.35 8.64,8.06 9.21,7.64C10.17,6.91 10.67,5.79 10.67,4.69C10.67,3.91 10.37,3.19 9.86,2.58C9.58,2.24 10.41,-0.31 9.67,0.03C8.94,0.37 7.86,1.13 7.29,0.97C6.68,0.79 6.02,0.69 5.33,0.69C4.73,0.69 4.16,0.76 3.62,0.9C2.83,1.1 2.09,0.36 1.33,0.03C0.58,-0.29 0.99,2.34 0.77,2.62C0.28,3.22 0,3.93 0,4.69C0,5.79 0.6,6.91 1.56,7.64C2.21,8.12 3.01,8.42 3.91,8.58" transform="translate(2.6666666666666665 1.644694921083138) rotate(0 5.333333333333333 4.287969206125098)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0.58,0C0.19,0.43 0,0.83 0,1.21C0,1.59 0,2.56 0,4.12" transform="translate(6.000666666666666 10.220633333333332) rotate(0 0.2896166666666667 2.058116666666666)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0C0.37,0.48 0.55,0.91 0.55,1.29C0.55,1.68 0.55,2.64 0.55,4.18" transform="translate(9.781533333333332 10.158866666666666) rotate(0 0.2744333333333332 2.0890166666666663)"/><path id="路径 4" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0C0.3,0.04 0.52,0.17 0.67,0.41C0.88,0.77 1.69,2.1 2.61,2.1C3.22,2.1 3.68,2.1 4,2.1" transform="translate(2 10.405166666666666) rotate(0 2.0004 1.050416666666667)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 4) rotate(0 2 4)" d="M4,8L0,4L4,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M4,8L0,4L4,0" transform="translate(6.333333333333333 4) rotate(0 2 4)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 573 B

After

Width:  |  Height:  |  Size: 553 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(2 3.333333333333333) rotate(0 6 5.666666666666666)" d="M6,0C2.69,0 0,2.54 0,5.67C0,8.8 2.69,11.33 6,11.33C9.31,11.33 12,8.8 12,5.67C12,2.54 9.31,0 6,0Z " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4.64 6.715000010822796) rotate(14.999999999999998 1 1.3333333333333335)" d="M1,0C0.45,0 0,0.6 0,1.33C0,2.07 0.45,2.67 1,2.67C1.55,2.67 2,2.07 2,1.33C2,0.6 1.55,0 1,0Z " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(9.31 6.714999802665079) rotate(165.00000507213028 1.000000156118488 1.3333335414913166)" d="M1,0C0.45,0 0,0.6 0,1.33C0,2.07 0.45,2.67 1,2.67C1.55,2.67 2,2.07 2,1.33C2,0.6 1.55,0 1,0Z " /><path id="路径 4" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(9.666599999999999 2.492504620561264) rotate(0 2.4176172657482775 2.2535810230527007)" d="M4,4.51C5.04,3.47 5.15,1.77 4.1,0.73C3.06,-0.32 1.04,-0.2 0,0.84 " /><path id="路径 5" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.492667974925419 2.4926141635940393) rotate(0 2.4203326792039572 2.253609584869647)" d="M0.84,4.51C-0.2,3.47 -0.32,1.77 0.73,0.73C1.77,-0.32 3.8,-0.2 4.84,0.84 " /><path id="路径 6" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.5 11.67) rotate(0 1.6666666666666665 0.33333029691911636)" d="M0,0C0.17,0.43 0.73,1.09 1.67,0.29C2.6,1.09 3.17,0.43 3.33,0 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M6,0C2.69,0 0,2.54 0,5.67C0,8.8 2.69,11.33 6,11.33C9.31,11.33 12,8.8 12,5.67C12,2.54 9.31,0 6,0Z" transform="translate(2 3.333333333333333) rotate(0 6 5.666666666666666)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M1,0C0.45,0 0,0.6 0,1.33C0,2.07 0.45,2.67 1,2.67C1.55,2.67 2,2.07 2,1.33C2,0.6 1.55,0 1,0Z" transform="translate(4.64 6.715000010822796) rotate(14.999999999999998 1 1.3333333333333335)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M1,0C0.45,0 0,0.6 0,1.33C0,2.07 0.45,2.67 1,2.67C1.55,2.67 2,2.07 2,1.33C2,0.6 1.55,0 1,0Z" transform="translate(9.31 6.714999802665079) rotate(165.00000507213028 1.000000156118488 1.3333335414913166)"/><path id="路径 4" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M4,4.51C5.04,3.47 5.15,1.77 4.1,0.73C3.06,-0.32 1.04,-0.2 0,0.84" transform="translate(9.666599999999999 2.492504620561264) rotate(0 2.4176172657482775 2.2535810230527007)"/><path id="路径 5" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0.84,4.51C-0.2,3.47 -0.32,1.77 0.73,0.73C1.77,-0.32 3.8,-0.2 4.84,0.84" transform="translate(1.492667974925419 2.4926141635940393) rotate(0 2.4203326792039572 2.253609584869647)"/><path id="路径 6" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0C0.17,0.43 0.73,1.09 1.67,0.29C2.6,1.09 3.17,0.43 3.33,0" transform="translate(6.5 11.67) rotate(0 1.6666666666666665 0.33333029691911636)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,41 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"
d="M0,0L3.33,3.3 " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"
d="M0,3.3L3.33,0 " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"
d="M3.3,3.3L0,0 " />
<path id="路径 4"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"
d="M3.3,0L0,3.3 " />
<path id="路径 5"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(11 2) rotate(0 1.5 1.5)" d="M0,0L3,0L3,3 " />
<path id="路径 6"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(11 11) rotate(0 1.5 1.5)" d="M3,0L3,3L0,3 " />
<path id="路径 7"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 11) rotate(0 1.5 1.5)" d="M3,3L0,3L0,0 " />
<path id="路径 8"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 2) rotate(0 1.5 1.5)" d="M0,3L0,0L3,0 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L3.33,3.3" transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3.3L3.33,0" transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3.3,3.3L0,0" transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"/><path id="路径 4" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3.3,0L0,3.3" transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"/><path id="路径 5" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L3,0L3,3" transform="translate(11 2) rotate(0 1.5 1.5)"/><path id="路径 6" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3,0L3,3L0,3" transform="translate(11 11) rotate(0 1.5 1.5)"/><path id="路径 7" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3,3L0,3L0,0" transform="translate(2 11) rotate(0 1.5 1.5)"/><path id="路径 8" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3L0,0L3,0" transform="translate(2 2) rotate(0 1.5 1.5)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,25 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.649903333333333 3.983233333333333) rotate(0 5.333331666666666 0)"
d="M0,0L10.67,0 " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.649903333333333 7.983233333333333) rotate(0 5.333331666666666 0)"
d="M0,0L10.67,0 " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.649903333333333 11.983233333333333) rotate(0 5.333331666666666 0)"
d="M0,0L10.67,0 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L10.67,0" transform="translate(2.649903333333333 3.983233333333333) rotate(0 5.333331666666666 0)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L10.67,0" transform="translate(2.649903333333333 7.983233333333333) rotate(0 5.333331666666666 0)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L10.67,0" transform="translate(2.649903333333333 11.983233333333333) rotate(0 5.333331666666666 0)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1020 B

View File

@@ -1,45 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"
d="M0,0L3.33,3.3 " />
<path id="路径 2"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"
d="M0,3.3L3.33,0 " />
<path id="路径 3"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"
d="M3.3,3.3L0,0 " />
<path id="路径 4"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"
d="M3.3,0L0,3.3 " />
<path id="路径 5"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.666666666666666 2.333333333333333) rotate(0 1.5 1.5)"
d="M0,0L0,3L3,3 " />
<path id="路径 6"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.333333333333333 2.333333333333333) rotate(0 1.5 1.5)"
d="M3,0L3,3L0,3 " />
<path id="路径 7"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2.333333333333333 10.666666666666666) rotate(0 1.5 1.5)"
d="M3,3L3,0L0,0 " />
<path id="路径 8"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(10.666666666666666 10.666666666666666) rotate(0 1.4832500000000004 1.5)"
d="M0,3L0,0L2.97,0 " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L3.33,3.3" transform="translate(2 2) rotate(0 1.6666666666666665 1.6499166666666665)"/><path id="路径 2" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3.3L3.33,0" transform="translate(2 10.666666666666666) rotate(0 1.6666666666666665 1.6499166666666671)"/><path id="路径 3" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3.3,3.3L0,0" transform="translate(10.700199999999999 10.666666666666666) rotate(0 1.6499166666666671 1.6499166666666671)"/><path id="路径 4" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3.3,0L0,3.3" transform="translate(10.666666666666666 2) rotate(0 1.6499166666666671 1.6499166666666665)"/><path id="路径 5" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,0L0,3L3,3" transform="translate(10.666666666666666 2.333333333333333) rotate(0 1.5 1.5)"/><path id="路径 6" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3,0L3,3L0,3" transform="translate(2.333333333333333 2.333333333333333) rotate(0 1.5 1.5)"/><path id="路径 7" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M3,3L3,0L0,0" transform="translate(2.333333333333333 10.666666666666666) rotate(0 1.5 1.5)"/><path id="路径 8" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M0,3L0,0L2.97,0" transform="translate(10.666666666666666 10.666666666666666) rotate(0 1.4832500000000004 1.5)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

1
app/icons/pin.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="fill:#333333; opacity:1;" d="M6.94515,1.72485c0.26101,0.26101 0.39152,0.57606 0.39152,0.94515v3.33c0,0.36909 -0.13051,0.68414 -0.39152,0.94515c-0.26101,0.26101 -0.57606,0.39152 -0.94515,0.39152h-3.33c-0.36909,0 -0.68414,-0.13051 -0.94515,-0.39152c-0.26101,-0.26101 -0.39152,-0.57606 -0.39152,-0.94515v-3.33c0,-0.36909 0.13051,-0.68414 0.39152,-0.94515c0.26101,-0.26101 0.57606,-0.39152 0.94515,-0.39152h3.33c0.36909,0 0.68414,0.13051 0.94515,0.39152zM2.66667,6c0,0.00222 0.00111,0.00333 0.00333,0.00333h3.33c0.00222,0 0.00333,-0.00111 0.00333,-0.00333v-3.33c0,-0.00222 -0.00111,-0.00333 -0.00333,-0.00333h-3.33c-0.00222,0 -0.00333,0.00111 -0.00333,0.00333z"></path><path id="路径 2" style="fill:#333333; opacity:1;" d="M6.94515,9.05822c0.26101,0.26101 0.39152,0.57607 0.39152,0.94518v3.33c0,0.36907 -0.13051,0.6841 -0.39152,0.9451c-0.26101,0.261 -0.57606,0.3915 -0.94515,0.3915h-3.33c-0.36909,0 -0.68414,-0.1305 -0.94515,-0.3915c-0.26101,-0.261 -0.39152,-0.57603 -0.39152,-0.9451v-3.33c0,-0.36911 0.13051,-0.68417 0.39152,-0.94518c0.26101,-0.26101 0.57606,-0.39151 0.94515,-0.39151h3.33c0.36909,0 0.68414,0.1305 0.94515,0.39151zM2.66667,13.3334c0,0.0022 0.00111,0.0033 0.00333,0.0033h3.33c0.00222,0 0.00333,-0.0011 0.00333,-0.0033v-3.33c0,-0.00227 -0.00111,-0.0034 -0.00333,-0.0034h-3.33c-0.00222,0 -0.00333,0.00113 -0.00333,0.0034z"></path><path id="路径 3" style="fill:#333333; opacity:1;" d="M13.7885,6.45515c-0.27727,0.27728 -0.59567,0.4921 -0.9552,0.64445c-0.37287,0.15805 -0.76283,0.23707 -1.1699,0.23707c-0.40727,0 -0.797,-0.07913 -1.1692,-0.23738c-0.35875,-0.15255 -0.67621,-0.36773 -0.95237,-0.64553c-0.27567,-0.2773 -0.48912,-0.59581 -0.64036,-0.95553c-0.15651,-0.37226 -0.23476,-0.76167 -0.23476,-1.16823c0,-0.40677 0.07836,-0.7959 0.23507,-1.1674c0.15143,-0.35898 0.36525,-0.67656 0.64144,-0.95275c0.27619,-0.27619 0.59379,-0.49001 0.95278,-0.64144c0.37147,-0.15672 0.7606,-0.23508 1.1674,-0.23508c0.40653,0 0.79593,0.07826 1.1682,0.23477c0.35973,0.15124 0.67823,0.36469 0.9555,0.64035c0.2778,0.27617 0.493,0.59363 0.6456,0.9524c0.1582,0.37217 0.2373,0.76189 0.2373,1.16915c0,0.40705 -0.079,0.79704 -0.237,1.16997c-0.1524,0.35951 -0.36723,0.67791 -0.6445,0.95518zM11.6634,2.66667c-0.46093,0 -0.8534,0.16199 -1.1774,0.48598c-0.324,0.32399 -0.486,0.71644 -0.486,1.17735c0,0.46231 0.16247,0.85689 0.4874,1.18374c0.32447,0.32639 0.71647,0.48959 1.176,0.48959c0.46087,0 0.85497,-0.16366 1.1823,-0.49098c0.32733,-0.32733 0.491,-0.72144 0.491,-1.18235c0,-0.4595 -0.1632,-0.85148 -0.4896,-1.17595c-0.32687,-0.32492 -0.72143,-0.48738 -1.1837,-0.48738z"></path><path id="路径 4" style="fill:#333333; opacity:1;" d="M14.2785,9.05822c0.261,0.26101 0.3915,0.57607 0.3915,0.94518v3.33c0,0.36907 -0.1305,0.6841 -0.3915,0.9451c-0.261,0.261 -0.57603,0.3915 -0.9451,0.3915h-3.33c-0.36911,0 -0.68417,-0.1305 -0.94518,-0.3915c-0.26101,-0.261 -0.39151,-0.57603 -0.39151,-0.9451v-3.33c0,-0.36911 0.1305,-0.68417 0.39151,-0.94518c0.26101,-0.26101 0.57607,-0.39151 0.94518,-0.39151h3.33c0.36907,0 0.6841,0.1305 0.9451,0.39151zM10,13.3334c0,0.0022 0.00113,0.0033 0.0034,0.0033h3.33c0.0022,0 0.0033,-0.0011 0.0033,-0.0033v-3.33c0,-0.00227 -0.0011,-0.0034 -0.0033,-0.0034h-3.33c-0.00227,0 -0.0034,0.00113 -0.0034,0.0034z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="fill:#333;opacity:1" d="M6.94515,1.72485c0.26101,0.26101 0.39152,0.57606 0.39152,0.94515v3.33c0,0.36909 -0.13051,0.68414 -0.39152,0.94515c-0.26101,0.26101 -0.57606,0.39152 -0.94515,0.39152h-3.33c-0.36909,0 -0.68414,-0.13051 -0.94515,-0.39152c-0.26101,-0.26101 -0.39152,-0.57606 -0.39152,-0.94515v-3.33c0,-0.36909 0.13051,-0.68414 0.39152,-0.94515c0.26101,-0.26101 0.57606,-0.39152 0.94515,-0.39152h3.33c0.36909,0 0.68414,0.13051 0.94515,0.39152zM2.66667,6c0,0.00222 0.00111,0.00333 0.00333,0.00333h3.33c0.00222,0 0.00333,-0.00111 0.00333,-0.00333v-3.33c0,-0.00222 -0.00111,-0.00333 -0.00333,-0.00333h-3.33c-0.00222,0 -0.00333,0.00111 -0.00333,0.00333z"/><path id="路径 2" style="fill:#333;opacity:1" d="M6.94515,9.05822c0.26101,0.26101 0.39152,0.57607 0.39152,0.94518v3.33c0,0.36907 -0.13051,0.6841 -0.39152,0.9451c-0.26101,0.261 -0.57606,0.3915 -0.94515,0.3915h-3.33c-0.36909,0 -0.68414,-0.1305 -0.94515,-0.3915c-0.26101,-0.261 -0.39152,-0.57603 -0.39152,-0.9451v-3.33c0,-0.36911 0.13051,-0.68417 0.39152,-0.94518c0.26101,-0.26101 0.57606,-0.39151 0.94515,-0.39151h3.33c0.36909,0 0.68414,0.1305 0.94515,0.39151zM2.66667,13.3334c0,0.0022 0.00111,0.0033 0.00333,0.0033h3.33c0.00222,0 0.00333,-0.0011 0.00333,-0.0033v-3.33c0,-0.00227 -0.00111,-0.0034 -0.00333,-0.0034h-3.33c-0.00222,0 -0.00333,0.00113 -0.00333,0.0034z"/><path id="路径 3" style="fill:#333;opacity:1" d="M13.7885,6.45515c-0.27727,0.27728 -0.59567,0.4921 -0.9552,0.64445c-0.37287,0.15805 -0.76283,0.23707 -1.1699,0.23707c-0.40727,0 -0.797,-0.07913 -1.1692,-0.23738c-0.35875,-0.15255 -0.67621,-0.36773 -0.95237,-0.64553c-0.27567,-0.2773 -0.48912,-0.59581 -0.64036,-0.95553c-0.15651,-0.37226 -0.23476,-0.76167 -0.23476,-1.16823c0,-0.40677 0.07836,-0.7959 0.23507,-1.1674c0.15143,-0.35898 0.36525,-0.67656 0.64144,-0.95275c0.27619,-0.27619 0.59379,-0.49001 0.95278,-0.64144c0.37147,-0.15672 0.7606,-0.23508 1.1674,-0.23508c0.40653,0 0.79593,0.07826 1.1682,0.23477c0.35973,0.15124 0.67823,0.36469 0.9555,0.64035c0.2778,0.27617 0.493,0.59363 0.6456,0.9524c0.1582,0.37217 0.2373,0.76189 0.2373,1.16915c0,0.40705 -0.079,0.79704 -0.237,1.16997c-0.1524,0.35951 -0.36723,0.67791 -0.6445,0.95518zM11.6634,2.66667c-0.46093,0 -0.8534,0.16199 -1.1774,0.48598c-0.324,0.32399 -0.486,0.71644 -0.486,1.17735c0,0.46231 0.16247,0.85689 0.4874,1.18374c0.32447,0.32639 0.71647,0.48959 1.176,0.48959c0.46087,0 0.85497,-0.16366 1.1823,-0.49098c0.32733,-0.32733 0.491,-0.72144 0.491,-1.18235c0,-0.4595 -0.1632,-0.85148 -0.4896,-1.17595c-0.32687,-0.32492 -0.72143,-0.48738 -1.1837,-0.48738z"/><path id="路径 4" style="fill:#333;opacity:1" d="M14.2785,9.05822c0.261,0.26101 0.3915,0.57607 0.3915,0.94518v3.33c0,0.36907 -0.1305,0.6841 -0.3915,0.9451c-0.261,0.261 -0.57603,0.3915 -0.9451,0.3915h-3.33c-0.36911,0 -0.68417,-0.1305 -0.94518,-0.3915c-0.26101,-0.261 -0.39151,-0.57603 -0.39151,-0.9451v-3.33c0,-0.36911 0.1305,-0.68417 0.39151,-0.94518c0.26101,-0.26101 0.57607,-0.39151 0.94518,-0.39151h3.33c0.36907,0 0.6841,0.1305 0.9451,0.39151zM10,13.3334c0,0.0022 0.00113,0.0033 0.0034,0.0033h3.33c0.0022,0 0.0033,-0.0011 0.0033,-0.0033v-3.33c0,-0.00227 -0.0011,-0.0034 -0.0033,-0.0034h-3.33c-0.00227,0 -0.0034,0.00113 -0.0034,0.0034z"/></g></g><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="分组 1" style="stroke:#333333; stroke-width:1.3; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(5.333333333333333 1.3333333333333333) rotate(0 4.666666666666666 4.666666666666666)" d="M1.36683 1.36683L2.77683 2.77683 M4.66667 0L4.66667 2 M4.66667 2L4.66667 0 M7.9623 1.36683L6.5523 2.77683 M6.5523 2.77683L7.9623 1.36683 M9.33333 4.66667L7.33333 4.66667 M7.33333 4.66667L9.33333 4.66667 M7.9623 7.9623L6.5523 6.5523 M6.5523 6.5523L7.9623 7.9623 M4.66667 9.33333L4.66667 7.33333 M4.66667 7.33333L4.66667 9.33333 M1.36683 7.9623L2.77683 6.5523 M2.77683 6.5523L1.36683 7.9623 M0 4.66667L2 4.66667 M2 4.66667L0 4.66667 " /><path id="路径 9" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.847983333333333 6.1381) rotate(0 4.006941666666666 4.006933333333333)" d="M8.01,0L0,8.01 " /></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="分组 1" style="stroke:#333;stroke-width:1.3;stroke-opacity:1;stroke-dasharray:0 0" d="M1.36683 1.36683L2.77683 2.77683 M4.66667 0L4.66667 2 M4.66667 2L4.66667 0 M7.9623 1.36683L6.5523 2.77683 M6.5523 2.77683L7.9623 1.36683 M9.33333 4.66667L7.33333 4.66667 M7.33333 4.66667L9.33333 4.66667 M7.9623 7.9623L6.5523 6.5523 M6.5523 6.5523L7.9623 7.9623 M4.66667 9.33333L4.66667 7.33333 M4.66667 7.33333L4.66667 9.33333 M1.36683 7.9623L2.77683 6.5523 M2.77683 6.5523L1.36683 7.9623 M0 4.66667L2 4.66667 M2 4.66667L0 4.66667" transform="translate(5.333333333333333 1.3333333333333333) rotate(0 4.666666666666666 4.666666666666666)"/><path id="路径 9" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M8.01,0L0,8.01" transform="translate(1.847983333333333 6.1381) rotate(0 4.006941666666666 4.006933333333333)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

1
app/icons/robot.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,17 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16"
height="16" viewBox="0 0 16 16" fill="none">
<defs>
<rect id="path_0" x="0" y="0" width="16" height="16" />
</defs>
<g opacity="1" transform="translate(0 0) rotate(0 8 8)">
<mask id="bg-mask-0" fill="white">
<use xlink:href="#path_0"></use>
</mask>
<g mask="url(#bg-mask-0)">
<path id="路径 1"
style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0"
transform="translate(2 1.3333333333333333) rotate(0 6.333333333333333 6.5)"
d="M6.67,3.67C1.67,3.67 0,7.33 0,13C0,13 2,8 6.67,8L6.67,11.67L12.67,6L6.67,0L6.67,3.67Z " />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 16 16"><defs><rect id="path_0" width="16" height="16" x="0" y="0"/></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="#fff"><use xlink:href="#path_0"/></mask><g mask="url(#bg-mask-0)"><path id="路径 1" style="stroke:#333;stroke-width:1.3333333333333333;stroke-opacity:1;stroke-dasharray:0 0" d="M6.67,3.67C1.67,3.67 0,7.33 0,13C0,13 2,8 6.67,8L6.67,11.67L12.67,6L6.67,0L6.67,3.67Z" transform="translate(2 1.3333333333333333) rotate(0 6.333333333333333 6.5)"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 645 B

View File

@@ -1,33 +1 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="30" height="14" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="#fff">
<circle cx="15" cy="15" r="15" fill="var(--primary, red)">
<animate attributeName="r" from="15" to="15"
begin="0s" dur="0.8s"
values="15;9;15" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="fill-opacity" from="1" to="1"
begin="0s" dur="0.8s"
values="1;.5;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3" fill="var(--primary, red)">
<animate attributeName="r" from="9" to="9"
begin="0s" dur="0.8s"
values="9;15;9" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="fill-opacity" from="0.5" to="0.5"
begin="0s" dur="0.8s"
values=".5;1;.5" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="105" cy="15" r="15" fill="var(--primary, red)">
<animate attributeName="r" from="15" to="15"
begin="0s" dur="0.8s"
values="15;9;15" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="fill-opacity" from="1" to="1"
begin="0s" dur="0.8s"
values="1;.5;1" calcMode="linear"
repeatCount="indefinite" />
</circle>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="14" fill="#fff" viewBox="0 0 120 30"><circle cx="15" cy="15" r="15" fill="var(--primary, red)"><animate attributeName="r" begin="0s" calcMode="linear" dur="0.8s" from="15" repeatCount="indefinite" to="15" values="15;9;15"/><animate attributeName="fill-opacity" begin="0s" calcMode="linear" dur="0.8s" from="1" repeatCount="indefinite" to="1" values="1;.5;1"/></circle><circle cx="60" cy="15" r="9" fill="var(--primary, red)" fill-opacity=".3"><animate attributeName="r" begin="0s" calcMode="linear" dur="0.8s" from="9" repeatCount="indefinite" to="9" values="9;15;9"/><animate attributeName="fill-opacity" begin="0s" calcMode="linear" dur="0.8s" from=".5" repeatCount="indefinite" to=".5" values=".5;1;.5"/></circle><circle cx="105" cy="15" r="15" fill="var(--primary, red)"><animate attributeName="r" begin="0s" calcMode="linear" dur="0.8s" from="15" repeatCount="indefinite" to="15" values="15;9;15"/><animate attributeName="fill-opacity" begin="0s" calcMode="linear" dur="0.8s" from="1" repeatCount="indefinite" to="1" values="1;.5;1"/></circle></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2,11 +2,10 @@
import "./styles/globals.scss";
import "./styles/markdown.scss";
import "./styles/highlight.scss";
import { getBuildConfig } from "./config/build";
import { getClientConfig } from "./config/client";
import { type Metadata } from "next";
const buildConfig = getBuildConfig();
export const metadata = {
export const metadata: Metadata = {
title: "ChatGPT Next Web",
description: "Your personal ChatGPT Chat Bot.",
viewport: {
@@ -32,7 +31,7 @@ export default function RootLayout({
return (
<html lang="en">
<head>
<meta name="version" content={buildConfig.commitId} />
<meta name="config" content={JSON.stringify(getClientConfig())} />
<link rel="manifest" href="/site.webmanifest"></link>
<script src="/serviceWorkerRegister.js" defer></script>
</head>

289
app/locales/ar.ts Normal file
View File

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

334
app/locales/bn.ts Normal file
View File

@@ -0,0 +1,334 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "./index";
const bn: PartialLocaleType = {
WIP: "শীঘ্রই আসছে...",
Error: {
Unauthorized:
"অননুমোদিত অ্যাক্সেস, অনুগ্রহ করে [অথোরাইজশন](/#/auth) পৃষ্ঠায় অ্যাক্সেস কোড ইনপুট করুন।",
},
Auth: {
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
SubTips: "অথবা আপনার OpenAI API কী প্রবেশ করুন",
Input: "অ্যাক্সেস কোড",
Confirm: "নিশ্চিত করুন",
Later: "পরে",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} টি বার্তা`,
},
Chat: {
SubTitle: (count: number) => `${count} টি বার্তা`,
Actions: {
ChatList: "চ্যাট তালিকায় যান",
CompressedHistory: "সংক্ষিপ্ত ইতিহাস মেমোরি প্রম্পট",
Export: "সমস্ত বার্তা মার্কডাউন হিসাবে রপ্তানি করুন",
Copy: "কপি",
Stop: "বন্ধ করুন",
Retry: "পুনরায় চেষ্টা করুন",
Pin: "পিন করুন",
PinToastContent: "পিন করা হয়েছে ২টি বার্তা প্রম্পটে",
PinToastAction: "দেখুন",
Delete: "মুছে ফেলুন",
Edit: "সম্পাদন করুন",
},
Commands: {
new: "নতুন চ্যাট শুরু করুন",
newm: "মাস্ক সহ নতুন চ্যাট শুরু করুন",
next: "পরবর্তী চ্যাট",
prev: "পূর্ববর্তী চ্যাট",
clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
del: "চ্যাট মুছুন",
},
InputActions: {
Stop: "বন্ধ করুন",
ToBottom: "সর্বশেষতম দিকে",
Theme: {
auto: "অটো",
light: "হালকা থিম",
dark: "ডার্ক থিম",
},
Prompt: "প্রম্পটগুলিতে",
Masks: "মাস্কগুলি",
Clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
Settings: "সেটিংস",
},
Rename: "চ্যাট পুনঃনামকরণ করুন",
Typing: "টাইপিং...",
Input: (submitKey: string) => {
var inputHints = `${submitKey} to send`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter to wrap";
}
return inputHints + ", / to search prompts, : to use commands";
},
Send: "প্রেরণ করুন",
Config: {
Reset: "ডিফল্টে রিসেট করুন",
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
},
},
Export: {
Title: "বার্তা রপ্তানিকরণ",
Copy: "সমস্তটি কপি করুন",
Download: "ডাউনলোড করুন",
MessageFromYou: "আপনার বার্তা",
MessageFromChatGPT: "চ্যাটজিপিটির বার্তা",
Share: "শেয়ার করুন শেয়ারজিপিটি তে",
Format: {
Title: "রপ্তানি ফরম্যাট",
SubTitle: "মার্কডাউন বা পিএনজি চিত্র",
},
IncludeContext: {
Title: "মাস্ক অন্তর্ভুক্ত করুন",
SubTitle: "মাস্কগুলি সংরক্ষণ করবেন না কি",
},
Steps: {
Select: "নির্বাচন করুন",
Preview: "প্রিভিউ করুন",
},
},
Select: {
Search: "অনুসন্ধান করুন",
All: "সমস্তটি নির্বাচন করুন",
Latest: "সর্বশেষতমটি নির্বাচন করুন",
Clear: "পরিষ্কার করুন",
},
Memory: {
Title: "মেমোরি প্রম্পট",
EmptyContent: "এখনও কিছুই নেই।",
Send: "মেমোরি প্রেরণ করুন",
Copy: "মেমোরি কপি করুন",
Reset: "পুনরায় নিশ্চিত করুন",
ResetConfirm:
"রিসেট করলে বর্তমান চ্যাট ইতিহাস এবং ঐতিহাসিক মেমোরি মুছে যাবে। পুনরায় নির্দিষ্ট করতে চান তা নিশ্চিত করতে চান?",
},
Home: {
NewChat: "নতুন চ্যাট",
DeleteChat: "নির্বাচিত সংলাপটি মুছতে নিশ্চিত করুন?",
DeleteToast: "চ্যাটটি মুছেছেন",
Revert: "পুনরায়",
},
Settings: {
Title: "সেটিংস",
SubTitle: "সমস্ত সেটিংস",
Danger: {
Reset: {
Title: "সমস্ত সেটিংস পুনঃনির্দেশ দিন",
SubTitle: "সকল সেটিংস ডিফল্টে পুনঃনির্দেশ দিতে",
Action: "পুনঃনির্দেশ দিন",
Confirm: "সমস্ত সেটিংস ডিফল্টে পুনঃনির্দেশ করতে নিশ্চিত করতে?",
},
Clear: {
Title: "সমস্ত তথ্য মুছুন",
SubTitle: "সমস্ত বার্তা এবং সেটিংস মুছুন",
Action: "মুছুন",
Confirm: "সমস্ত বার্তা এবং সেটিংস মুছে ফেলতে নিশ্চিত করতে?",
},
},
Lang: {
Name: "বাংলা", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "সমস্ত ভাষা",
},
Avatar: "অবতার",
FontSize: {
Title: "ফন্ট সাইজ",
SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন",
},
InjectSystemPrompts: {
Title: "حقن تلميحات النظام",
SubTitle:
"قم بإضافة تلميحة نظام محاكاة ChatGPT إلى بداية قائمة الرسائل المُطلَبة في كل طلب",
},
InputTemplate: {
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",
},
SendKey: "প্রেরণ চাবি",
Theme: "থিম",
TightBorder: "সঙ্গতি সীমা",
SendPreviewBubble: {
Title: "প্রিভিউ বুলবুল প্রেরণ করুন",
SubTitle: "বুলবুলে মার্কডাউন প্রিভিউ করুন",
},
Mask: {
Splash: {
Title: "মাস্ক স্প্ল্যাশ স্ক্রিন",
SubTitle:
"নতুন চ্যাট শুরু করার আগে মাস্ক স্প্ল্যাশ স্ক্রিন প্রদর্শন করুন",
},
Builtin: {
Title: "মূলত মাস্ক গোপন করুন",
SubTitle: "মাস্ক তালিকা থেকে মূলত মাস্কগুলি লুকান",
},
},
Prompt: {
Disable: {
Title: "অটো-সম্পূর্ণতা নিষ্ক্রিয় করুন",
SubTitle: "অটো-সম্পূর্ণতা চালু করতে / ইনপুট করুন",
},
List: "প্রম্পট তালিকা",
ListCount: (builtin: number, custom: number) =>
`${builtin} built-in, ${custom} user-defined`,
Edit: "সম্পাদন করুন",
Modal: {
Title: "প্রম্পট তালিকা",
Add: "একটি যোগ করুন",
Search: "সন্ধান প্রম্পট",
},
EditModal: {
Title: "সম্পাদন করুন প্রম্পট",
},
},
HistoryCount: {
Title: "সংযুক্ত বার্তা সংখ্যা",
SubTitle: "প্রতি অনুরোধে প্রেরণ করা গেলে প্রেরণ করা হবে",
},
CompressThreshold: {
Title: "ইতিহাস সঙ্কুচিত করার সীমা",
SubTitle:
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
},
Usage: {
Title: "একাউন্ট ব্যালেন্স",
SubTitle(used: any, total: any) {
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন $${total}`;
},
IsChecking: "চেক করা হচ্ছে...",
Check: "চেক",
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
},
Model: "মডেল",
Temperature: {
Title: "তাপমাত্রা",
SubTitle: "আরতি মান বেশি করলে বেশি এলোমেলো আউটপুট হবে",
},
TopP: {
Title: "শীর্ষ পি",
SubTitle: "তাপমাত্রা সঙ্গে এই মান পরিবর্তন করবেন না",
},
MaxTokens: {
Title: "সর্বাধিক টোকেন",
SubTitle: "ইনপুট টোকেন এবং উৎপাদিত টোকেনের সর্বাধিক দৈর্ঘ্য",
},
PresencePenalty: {
Title: "উপস্থিতির জরিমানা",
SubTitle: "আরতি মান বেশি করলে নতুন বিষয়গুলি সম্ভাব্যতা বাড়াতে পারে",
},
FrequencyPenalty: {
Title: "ফ্রিকুয়েন্সি জরিমানা",
SubTitle:
"আরতি মান বাড়ালে একই লাইন পুনরায় ব্যাবহার করার সম্ভাবনা হ্রাস পায়",
},
},
Store: {
DefaultTopic: "নতুন সংলাপ",
BotHello: "হ্যালো! আজকে আপনাকে কিভাবে সাহায্য করতে পারি?",
Error: "কিছু নিয়ে ভুল হয়েছে, পরে আবার চেষ্টা করুন।",
Prompt: {
History: (content: string) =>
"এটি চ্যাট ইতিহাসের সংক্ষিপ্ত সংকলনের মতো: " + content,
Topic:
"আমাদের সংলাপটির চার থেকে পাঁচ শব্দের একটি শিরোনাম তৈরি করুন যা আমাদের আলাপের সংক্ষিপ্তসার হিসাবে যোগ হবে না, যেমন অভিবৃত্তি, বিন্যাস, উদ্ধৃতি, পূর্বচালক চিহ্ন, পূর্বরোবক্তির যেকোনো চিহ্ন বা অতিরিক্ত পাঠ। মেয়াদশেষ উদ্ধৃতি চেষ্টা করুন।",
Summarize:
"২০০ শব্দের লম্বা হয়ে মুহূর্তে আলোচনা সংক্ষেপের রপ্তানি করুন, যেটি ভবিষ্যতের প্রম্পট হিসাবে ব্যবহার করবেন।",
},
},
Copy: {
Success: "ক্লিপবোর্ডে কপি করা হয়েছে",
Failed: "কপি ব্যর্থ, অনুমতি প্রদান করার জন্য অনুমতি প্রদান করুন",
},
Context: {
Toast: (x: any) => `With ${x} contextual prompts`,
Edit: "বর্তমান চ্যাট সেটিংস",
Add: "একটি প্রম্পট যোগ করুন",
Clear: "সঙ্গতি পরিস্কার করুন",
Revert: "পূর্ববর্তী অবস্থানে ফিরে যান",
},
Plugin: {
Name: "প্লাগইন",
},
FineTuned: {
Sysmessage: "আপনি একটি সহকারী যা",
},
Mask: {
Name: "মাস্ক",
Page: {
Title: "প্রম্পট টেমপ্লেট",
SubTitle: (count: number) => `${count} টি প্রম্পট টেমপ্লেট`,
Search: "টেমপ্লেট অনুসন্ধান করুন",
Create: "তৈরি করুন",
},
Item: {
Info: (count: number) => `${count} প্রম্পট`,
Chat: "চ্যাট",
View: "দেখুন",
Edit: "সম্পাদন করুন",
Delete: "মুছে ফেলুন",
DeleteConfirm: "মুছে ফেলতে নিশ্চিত করুন?",
},
EditModal: {
Title: (readonly: boolean) =>
`প্রম্পট টেমপ্লেট সম্পাদন করুন ${readonly ? "(readonly)" : ""}`,
Download: "ডাউনলোড করুন",
Clone: "ক্লোন করুন",
},
Config: {
Avatar: "বট অবতার",
Name: "বটের নাম",
Sync: {
Title: "গ্লোবাল কনফিগ ব্যবহার করুন",
SubTitle: "এই চ্যাটে গ্লোবাল কনফিগ ব্যবহার করুন",
Confirm:
"গ্লোবাল কনফিগ দ্বারা কাস্টম কনফিগ ওভাররাইড করতে নিশ্চিত করতে?",
},
HideContext: {
Title: "সংশ্লিষ্টতা প্রম্পটগুলি লুকান",
SubTitle: "চ্যাটে সংশ্লিষ্টতা প্রম্পটগুলি দেখাবেন না",
},
Share: {
Title: "এই মাস্কটি শেয়ার করুন",
SubTitle: "এই মাস্কের একটি লিঙ্ক তৈরি করুন",
Action: "লিঙ্ক কপি করুন",
},
},
},
NewChat: {
Return: "ফিরে যান",
Skip: "শুরু করুন",
Title: "মাস্ক নির্বাচন করুন",
SubTitle: "মাস্কের পিছনে আত্মার সঙ্গে চ্যাট করুন",
More: "আরো খুঁজুন",
NotShow: "এখনও দেখাবেন না",
ConfirmNoShow:
"নিষ্ক্রিয় করতে নিশ্চিত করুন? পরে আপনি এটি সেটিংসে সক্ষম করতে পারবেন।",
},
UI: {
Confirm: "নিশ্চিত করুন",
Cancel: "বাতিল করুন",
Close: "বন্ধ করুন",
Create: "তৈরি করুন",
Edit: "সম্পাদন করুন",
},
Exporter: {
Model: "মডেল",
Messages: "বার্তা",
Topic: "টপিক",
Time: "সময়",
},
};
export default bn;

View File

@@ -1,16 +1,35 @@
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
const isApp = !!getClientConfig()?.isApp;
const cn = {
WIP: "该功能仍在开发中……",
Error: {
Unauthorized:
"访问密码不正确或为空,请前往[设置](/#/settings)页输入正确的访问密码,或者填入你自己的 OpenAI API Key。",
Unauthorized: isApp
? "检测到无效 API Key,请前往[设置](/#/settings)页检查 API Key 是否配置正确。"
: "访问密码不正确或为空,请前往[登录](/#/auth)页输入正确的访问密码,或者在[设置](/#/settings)页填入你自己的 OpenAI API Key。",
},
Auth: {
Title: "需要密码",
Tips: "管理员开启了密码验证,请在下方填入访问码",
SubTips: "或者输入你的 OpenAI API 密钥",
Input: "在此处填写访问码",
Confirm: "确认",
Later: "稍后再说",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 条对话`,
},
Chat: {
SubTitle: (count: number) => `与 ChatGPT 的 ${count} 条对话`,
SubTitle: (count: number) => ` ${count} 条对话`,
EditMessage: {
Title: "编辑消息记录",
Topic: {
Title: "聊天主题",
SubTitle: "更改当前聊天主题",
},
},
Actions: {
ChatList: "查看消息列表",
CompressedHistory: "查看压缩后的历史 Prompt",
@@ -18,7 +37,32 @@ const cn = {
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: "对话设置",
},
Rename: "重命名对话",
Typing: "正在输入…",
@@ -27,28 +71,52 @@ const cn = {
if (submitKey === String(SubmitKey.Enter)) {
inputHints += "Shift + Enter 换行";
}
return inputHints + "/ 触发补全";
return inputHints + "/ 触发补全: 触发命令";
},
Send: "发送",
Config: {
Reset: "重置默认",
SaveAs: "存为面具",
Reset: "清除记忆",
SaveAs: "存为面具",
},
IsContext: "预设提示词",
},
Export: {
Title: "导出聊天记录为 Markdown",
Title: "分享聊天记录",
Copy: "全部复制",
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: "重置后将清空当前对话记录以及历史摘要,确认重置",
Reset: "[unused]",
ResetConfirm: "确认清空历史摘要",
},
Home: {
NewChat: "新的聊天",
@@ -58,38 +126,39 @@ const cn = {
},
Settings: {
Title: "设置",
SubTitle: "设置选项",
Actions: {
ClearAll: "清除所有数据",
ResetAll: "重置所有选项",
Close: "关闭",
ConfirmResetAll: "确认重置所有配置?",
ConfirmClearAll: "确认清除所有数据?",
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: "所有语言",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "头像",
FontSize: {
Title: "字体大小",
SubTitle: "聊天内容的字体大小",
},
InjectSystemPrompts: {
Title: "注入系统级提示信息",
SubTitle: "强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示",
},
InputTemplate: {
Title: "用户输入预处理",
SubTitle: "用户最新的一条消息会填充到此模板",
},
Update: {
Version: (x: string) => `当前版本:${x}`,
@@ -106,9 +175,62 @@ const cn = {
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: {
Title: "面具启动页",
SubTitle: "新建聊天时,展示面具启动页",
Splash: {
Title: "面具启动页",
SubTitle: "新建聊天时,展示面具启动页",
},
Builtin: {
Title: "隐藏内置面具",
SubTitle: "在所有面具列表中隐藏内置面具",
},
},
Prompt: {
Disable: {
@@ -136,11 +258,6 @@ const cn = {
Title: "历史消息长度压缩阈值",
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
},
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可绕过密码访问限制",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "余额查询",
@@ -151,16 +268,65 @@ const cn = {
Check: "重新检查",
NoAccess: "输入 API Key 或访问密码查看余额",
},
AccessCode: {
Title: "访问密码",
SubTitle: "管理员已开启加密访问",
Placeholder: "请输入访问密码",
Access: {
AccessCode: {
Title: "访问密码",
SubTitle: "管理员已开启加密访问",
Placeholder: "请输入访问密码",
},
CustomEndpoint: {
Title: "自定义接口",
SubTitle: "是否使用自定义 Azure 或 OpenAI 服务",
},
Provider: {
Title: "模型服务商",
SubTitle: "切换不同的服务商",
},
OpenAI: {
ApiKey: {
Title: "API Key",
SubTitle: "使用自定义 OpenAI Key 绕过密码访问限制",
Placeholder: "OpenAI API Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "除默认地址外,必须包含 http(s)://",
},
},
Azure: {
ApiKey: {
Title: "接口密钥",
SubTitle: "使用自定义 Azure Key 绕过密码访问限制",
Placeholder: "Azure API Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "样例:",
},
ApiVerion: {
Title: "接口版本 (azure api version)",
SubTitle: "选择指定的部分版本",
},
},
CustomModel: {
Title: "自定义模型名",
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
},
},
Model: "模型 (model)",
Temperature: {
Title: "随机性 (temperature)",
SubTitle: "值越大,回复越随机",
},
TopP: {
Title: "核采样 (top_p)",
SubTitle: "与随机性类似,但不要和随机性一起更改",
},
MaxTokens: {
Title: "单次回复限制 (max_tokens)",
SubTitle: "单次交互所用的最大 Token 数",
@@ -169,32 +335,44 @@ const cn = {
Title: "话题新鲜度 (presence_penalty)",
SubTitle: "值越大,越有可能扩展到新话题",
},
FrequencyPenalty: {
Title: "频率惩罚度 (frequency_penalty)",
SubTitle: "值越大,越有可能降低重复字词",
},
},
Store: {
DefaultTopic: "新的聊天",
BotHello: "有什么可以帮你的吗",
Error: "出错了,稍后重试吧",
Prompt: {
History: (content: string) =>
"这是 ai 和用户的历史聊天总结作为前情提要:" + content,
History: (content: string) => "这是历史聊天总结作为前情提要:" + content,
Topic:
"使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,如果没有主题,请直接返回“闲聊”",
Summarize:
"简要总结一下你和用户的对话,用作后续的上下文提示 prompt控制在 200 字以内",
"简要总结一下对话内容,用作后续的上下文提示 prompt控制在 200 字以内",
},
},
Copy: {
Success: "已写入剪切板",
Failed: "复制失败,请赋予剪切板权限",
},
Download: {
Success: "内容已下载到您的目录。",
Failed: "下载失败。",
},
Context: {
Toast: (x: any) => `已设置 ${x}前置上下文`,
Toast: (x: any) => `包含 ${x}预设提示词`,
Edit: "当前对话设置",
Add: "新增预设对话",
Add: "新增一条对话",
Clear: "上下文已清除",
Revert: "恢复上下文",
},
Plugin: {
Name: "插件",
},
FineTuned: {
Sysmessage: "你是一个助手",
},
Mask: {
Name: "面具",
Page: {
@@ -220,6 +398,20 @@ const cn = {
Config: {
Avatar: "角色头像",
Name: "角色名称",
Sync: {
Title: "使用全局设置",
SubTitle: "当前对话是否使用全局模型设置",
Confirm: "当前对话的自定义设置将会被自动覆盖,确认启用全局设置?",
},
HideContext: {
Title: "隐藏预设对话",
SubTitle: "隐藏后预设对话不会出现在聊天界面",
},
Share: {
Title: "分享此面具",
SubTitle: "生成此面具的直达链接",
Action: "复制链接",
},
},
},
NewChat: {
@@ -232,12 +424,27 @@ const cn = {
More: "查看全部",
},
URLCommand: {
Code: "检测到链接中已经包含访问码,是否自动填入?",
Settings: "检测到链接中包含了预制设置,是否自动填入?",
},
UI: {
Confirm: "确认",
Cancel: "取消",
Close: "关闭",
Create: "新建",
Edit: "编辑",
Export: "导出",
Import: "导入",
Sync: "同步",
Config: "配置",
},
Exporter: {
Model: "模型",
Messages: "消息",
Topic: "主题",
Time: "时间",
},
};
@@ -246,6 +453,8 @@ type DeepPartial<T> = T extends object
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export type LocaleType = DeepPartial<typeof cn>;
export type LocaleType = typeof cn;
export type PartialLocaleType = DeepPartial<typeof cn>;
export default cn;

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const cs: LocaleType = {
const cs: PartialLocaleType = {
WIP: "V přípravě...",
Error: {
Unauthorized:
"Neoprávněný přístup, zadejte přístupový kód na stránce nastavení.",
"Neoprávněný přístup, zadejte přístupový kód na [stránce](/#/auth) nastavení.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} zpráv`,
@@ -61,37 +61,21 @@ const cs: LocaleType = {
Settings: {
Title: "Nastavení",
SubTitle: "Všechna nastavení",
Actions: {
ClearAll: "Vymazat všechna data",
ResetAll: "Obnovit veškeré nastavení",
Close: "Zavřít",
ConfirmResetAll: "Jste si jisti, že chcete obnovit všechna nastavení?",
ConfirmClearAll: "Jste si jisti, že chcete smazat všechna data?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Všechny jazyky",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Velikost písma",
SubTitle: "Nastavení velikosti písma obsahu chatu",
},
InjectSystemPrompts: {
Title: "Vložit systémové prompty",
SubTitle:
"Vynutit přidání simulovaného systémového promptu ChatGPT na začátek seznamu zpráv každého požadavku",
},
Update: {
Version: (x: string) => `Verze: ${x}`,
IsLatest: "Aktuální verze",
@@ -108,8 +92,10 @@ const cs: LocaleType = {
SubTitle: "Zobrazit v náhledu bubliny",
},
Mask: {
Title: "Úvodní obrazovka Masek",
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
Splash: {
Title: "Úvodní obrazovka Masek",
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
},
},
Prompt: {
Disable: {
@@ -138,11 +124,7 @@ const cs: LocaleType = {
SubTitle:
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
},
Token: {
Title: "API klíč",
SubTitle: "Použitím klíče ignorujete omezení přístupového kódu",
Placeholder: "Klíč API OpenAI",
},
Usage: {
Title: "Stav účtu",
SubTitle(used: any, total: any) {
@@ -152,11 +134,7 @@ const cs: LocaleType = {
Check: "Zkontrolovat",
NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
},
AccessCode: {
Title: "Přístupový kód",
SubTitle: "Kontrola přístupu povolena",
Placeholder: "Potřebujete přístupový kód",
},
Model: "Model",
Temperature: {
Title: "Teplota",
@@ -170,6 +148,11 @@ const cs: LocaleType = {
Title: "Přítomnostní korekce",
SubTitle: "Větší hodnota zvyšuje pravděpodobnost nových témat.",
},
FrequencyPenalty: {
Title: "Frekvenční penalizace",
SubTitle:
"Větší hodnota snižující pravděpodobnost opakování stejného řádku",
},
},
Store: {
DefaultTopic: "Nová konverzace",
@@ -197,6 +180,9 @@ const cs: LocaleType = {
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Jste asistent, který",
},
Mask: {
Name: "Maska",
Page: {
@@ -241,6 +227,12 @@ const cs: LocaleType = {
Create: "Vytvořit",
Edit: "Upravit",
},
Exporter: {
Model: "Model",
Messages: "Zprávy",
Topic: "Téma",
Time: "Čas",
},
};
export default cs;

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const de: LocaleType = {
const de: PartialLocaleType = {
WIP: "In Bearbeitung...",
Error: {
Unauthorized:
"Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der Einstellungsseite ein.",
"Unbefugter Zugriff, bitte geben Sie den Zugangscode auf der [Einstellungsseite](/#/auth) ein.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} Nachrichten`,
@@ -61,38 +61,21 @@ const de: LocaleType = {
Settings: {
Title: "Einstellungen",
SubTitle: "Alle Einstellungen",
Actions: {
ClearAll: "Alle Daten löschen",
ResetAll: "Alle Einstellungen zurücksetzen",
Close: "Schließen",
ConfirmResetAll:
"Möchten Sie wirklich alle Konfigurationen zurücksetzen?",
ConfirmClearAll: "Möchten Sie wirklich alle Chats zurücksetzen?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Alle Sprachen",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Schriftgröße",
SubTitle: "Schriftgröße des Chat-Inhalts anpassen",
},
InjectSystemPrompts: {
Title: "System-Prompts einfügen",
SubTitle:
"Erzwingt das Hinzufügen eines simulierten systemweiten Prompts von ChatGPT am Anfang der Nachrichtenliste bei jeder Anfrage",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Neueste Version",
@@ -109,8 +92,10 @@ const de: LocaleType = {
SubTitle: "Preview markdown in bubble",
},
Mask: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
},
Prompt: {
Disable: {
@@ -139,12 +124,7 @@ const de: LocaleType = {
SubTitle:
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
},
Token: {
Title: "API-Schlüssel",
SubTitle:
"Verwenden Sie Ihren Schlüssel, um das Zugangscode-Limit zu ignorieren",
Placeholder: "OpenAI API-Schlüssel",
},
Usage: {
Title: "Kontostand",
SubTitle(used: any, total: any) {
@@ -154,11 +134,6 @@ const de: LocaleType = {
Check: "Erneut prüfen",
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
},
AccessCode: {
Title: "Zugangscode",
SubTitle: "Zugangskontrolle aktiviert",
Placeholder: "Zugangscode erforderlich",
},
Model: "Modell",
Temperature: {
Title: "Temperature", //Temperatur
@@ -173,6 +148,11 @@ const de: LocaleType = {
SubTitle:
"Ein größerer Wert erhöht die Wahrscheinlichkeit, dass über neue Themen gesprochen wird",
},
FrequencyPenalty: {
Title: "Frequency Penalty", // HäufigkeitStrafe
SubTitle:
"Ein größerer Wert, der die Wahrscheinlichkeit verringert, dass dieselbe Zeile wiederholt wird",
},
},
Store: {
DefaultTopic: "Neues Gespräch",
@@ -202,6 +182,9 @@ const de: LocaleType = {
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Du bist ein Assistent, der",
},
Mask: {
Name: "Mask",
Page: {
@@ -246,6 +229,12 @@ const de: LocaleType = {
Create: "Create",
Edit: "Edit",
},
Exporter: {
Model: "Modell",
Messages: "Nachrichten",
Topic: "Thema",
Time: "Zeit",
},
};
export default de;

View File

@@ -1,17 +1,37 @@
import { getClientConfig } from "../config/client";
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import { LocaleType } from "./index";
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
const isApp = !!getClientConfig()?.isApp;
const en: LocaleType = {
WIP: "Coming Soon...",
Error: {
Unauthorized:
"Unauthorized access, please enter access code in settings page.",
Unauthorized: isApp
? "Invalid API Key, please check it in [Settings](/#/settings) page."
: "Unauthorized access, please enter access code in [auth](/#/auth) page, or enter your OpenAI API Key.",
},
Auth: {
Title: "Need Access Code",
Tips: "Please enter access code below",
SubTips: "Or enter your OpenAI API Key",
Input: "access code",
Confirm: "Confirm",
Later: "Later",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} messages`,
},
Chat: {
SubTitle: (count: number) => `${count} messages with ChatGPT`,
SubTitle: (count: number) => `${count} messages`,
EditMessage: {
Title: "Edit All Messages",
Topic: {
Title: "Topic",
SubTitle: "Change the current topic",
},
},
Actions: {
ChatList: "Go To Chat List",
CompressedHistory: "Compressed History Memory Prompt",
@@ -19,7 +39,32 @@ const en: LocaleType = {
Copy: "Copy",
Stop: "Stop",
Retry: "Retry",
Pin: "Pin",
PinToastContent: "Pinned 1 messages to contextual prompts",
PinToastAction: "View",
Delete: "Delete",
Edit: "Edit",
},
Commands: {
new: "Start a new chat",
newm: "Start a new chat with mask",
next: "Next Chat",
prev: "Previous Chat",
clear: "Clear Context",
del: "Delete Chat",
},
InputActions: {
Stop: "Stop",
ToBottom: "To Latest",
Theme: {
auto: "Auto",
light: "Light Theme",
dark: "Dark Theme",
},
Prompt: "Prompts",
Masks: "Masks",
Clear: "Clear Context",
Settings: "Settings",
},
Rename: "Rename Chat",
Typing: "Typing…",
@@ -28,20 +73,44 @@ const en: LocaleType = {
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter to wrap";
}
return inputHints + ", / to search prompts";
return inputHints + ", / to search prompts, : to use commands";
},
Send: "Send",
Config: {
Reset: "Reset to Default",
SaveAs: "Save as Mask",
},
IsContext: "Contextual Prompt",
},
Export: {
Title: "All Messages",
Title: "Export Messages",
Copy: "Copy All",
Download: "Download",
MessageFromYou: "Message From You",
MessageFromChatGPT: "Message From ChatGPT",
Share: "Share to ShareGPT",
Format: {
Title: "Export Format",
SubTitle: "Markdown or PNG Image",
},
IncludeContext: {
Title: "Including Context",
SubTitle: "Export context prompts in mask or not",
},
Steps: {
Select: "Select",
Preview: "Preview",
},
Image: {
Toast: "Capturing Image...",
Modal: "Long press or right click to save image",
},
},
Select: {
Search: "Search",
All: "Select All",
Latest: "Select Latest",
Clear: "Clear",
},
Memory: {
Title: "Memory Prompt",
@@ -61,37 +130,38 @@ const en: LocaleType = {
Settings: {
Title: "Settings",
SubTitle: "All Settings",
Actions: {
ClearAll: "Clear All Data",
ResetAll: "Reset All Settings",
Close: "Close",
ConfirmResetAll: "Are you sure you want to reset all configurations?",
ConfirmClearAll: "Are you sure you want to reset all data?",
Danger: {
Reset: {
Title: "Reset All Settings",
SubTitle: "Reset all setting items to default",
Action: "Reset",
Confirm: "Confirm to reset all settings to default?",
},
Clear: {
Title: "Clear All Data",
SubTitle: "Clear all messages and settings",
Action: "Clear",
Confirm: "Confirm to clear all messages and settings?",
},
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "All Languages",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Font Size",
SubTitle: "Adjust font size of chat content",
},
InjectSystemPrompts: {
Title: "Inject System Prompts",
SubTitle: "Inject a global system prompt for every request",
},
InputTemplate: {
Title: "Input Template",
SubTitle: "Newest message will be filled to this template",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Latest version",
@@ -107,9 +177,63 @@ const en: LocaleType = {
Title: "Send Preview Bubble",
SubTitle: "Preview markdown in bubble",
},
AutoGenerateTitle: {
Title: "Auto Generate Title",
SubTitle: "Generate a suitable title based on the conversation content",
},
Sync: {
CloudState: "Last Update",
NotSyncYet: "Not sync yet",
Success: "Sync Success",
Fail: "Sync Fail",
Config: {
Modal: {
Title: "Config Sync",
Check: "Check Connection",
},
SyncType: {
Title: "Sync Type",
SubTitle: "Choose your favorite sync service",
},
Proxy: {
Title: "Enable CORS Proxy",
SubTitle: "Enable a proxy to avoid cross-origin restrictions",
},
ProxyUrl: {
Title: "Proxy Endpoint",
SubTitle:
"Only applicable to the built-in CORS proxy for this project",
},
WebDav: {
Endpoint: "WebDAV Endpoint",
UserName: "User Name",
Password: "Password",
},
UpStash: {
Endpoint: "UpStash Redis REST Url",
UserName: "Backup Name",
Password: "UpStash Redis REST Token",
},
},
LocalState: "Local Data",
Overview: (overview: any) => {
return `${overview.chat} chats${overview.message} messages${overview.prompt} prompts${overview.mask} masks`;
},
ImportFailed: "Failed to import from file",
},
Mask: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
Builtin: {
Title: "Hide Builtin Masks",
SubTitle: "Hide builtin masks in mask list",
},
},
Prompt: {
Disable: {
@@ -138,11 +262,7 @@ const en: LocaleType = {
SubTitle:
"Will compress if uncompressed messages length exceeds the value",
},
Token: {
Title: "API Key",
SubTitle: "Use your key to ignore access code limit",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "Account Balance",
SubTitle(used: any, total: any) {
@@ -152,16 +272,64 @@ const en: LocaleType = {
Check: "Check",
NoAccess: "Enter API Key to check balance",
},
AccessCode: {
Title: "Access Code",
SubTitle: "Access control enabled",
Placeholder: "Need Access Code",
Access: {
AccessCode: {
Title: "Access Code",
SubTitle: "Access control Enabled",
Placeholder: "Enter Code",
},
CustomEndpoint: {
Title: "Custom Endpoint",
SubTitle: "Use custom Azure or OpenAI service",
},
Provider: {
Title: "Model Provider",
SubTitle: "Select Azure or OpenAI",
},
OpenAI: {
ApiKey: {
Title: "OpenAI API Key",
SubTitle: "User custom OpenAI Api Key",
Placeholder: "sk-xxx",
},
Endpoint: {
Title: "OpenAI Endpoint",
SubTitle: "Must starts with http(s):// or use /api/openai as default",
},
},
Azure: {
ApiKey: {
Title: "Azure Api Key",
SubTitle: "Check your api key from Azure console",
Placeholder: "Azure Api Key",
},
Endpoint: {
Title: "Azure Endpoint",
SubTitle: "Example: ",
},
ApiVerion: {
Title: "Azure Api Version",
SubTitle: "Check your api version from azure console",
},
},
CustomModel: {
Title: "Custom Models",
SubTitle: "Custom model options, seperated by comma",
},
},
Model: "Model",
Temperature: {
Title: "Temperature",
SubTitle: "A larger value makes the more random output",
},
TopP: {
Title: "Top P",
SubTitle: "Do not alter this value together with temperature",
},
MaxTokens: {
Title: "Max Tokens",
SubTitle: "Maximum length of input tokens and generated tokens",
@@ -171,6 +339,11 @@ const en: LocaleType = {
SubTitle:
"A larger value increases the likelihood to talk about new topics",
},
FrequencyPenalty: {
Title: "Frequency Penalty",
SubTitle:
"A larger value decreasing the likelihood to repeat the same line",
},
},
Store: {
DefaultTopic: "New Conversation",
@@ -178,26 +351,34 @@ const en: LocaleType = {
Error: "Something went wrong, please try again later.",
Prompt: {
History: (content: string) =>
"This is a summary of the chat history between the AI and the user as a recap: " +
content,
"This is a summary of the chat history as a recap: " + content,
Topic:
"Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, or additional text. Remove enclosing quotation marks.",
Summarize:
"Summarize our discussion briefly in 200 words or less to use as a prompt for future context.",
"Summarize the discussion briefly in 200 words or less to use as a prompt for future context.",
},
},
Copy: {
Success: "Copied to clipboard",
Failed: "Copy failed, please grant permission to access clipboard",
},
Download: {
Success: "Content downloaded to your directory.",
Failed: "Download failed.",
},
Context: {
Toast: (x: any) => `With ${x} contextual prompts`,
Edit: "Contextual and Memory Prompts",
Edit: "Current Chat Settings",
Add: "Add a Prompt",
Clear: "Context Cleared",
Revert: "Revert",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "You are an assistant that",
},
Mask: {
Name: "Mask",
Page: {
@@ -223,15 +404,29 @@ const en: LocaleType = {
Config: {
Avatar: "Bot Avatar",
Name: "Bot Name",
Sync: {
Title: "Use Global Config",
SubTitle: "Use global config in this chat",
Confirm: "Confirm to override custom config with global config?",
},
HideContext: {
Title: "Hide Context Prompts",
SubTitle: "Do not show in-context prompts in chat",
},
Share: {
Title: "Share This Mask",
SubTitle: "Generate a link to this mask",
Action: "Copy Link",
},
},
},
NewChat: {
Return: "Return",
Skip: "Skip",
Skip: "Just Start",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Not Show Again",
NotShow: "Never Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
},
@@ -241,6 +436,21 @@ const en: LocaleType = {
Close: "Close",
Create: "Create",
Edit: "Edit",
Export: "Export",
Import: "Import",
Sync: "Sync",
Config: "Config",
},
Exporter: {
Model: "Model",
Messages: "Messages",
Topic: "Topic",
Time: "Time",
},
URLCommand: {
Code: "Detected access code from url, confirm to apply? ",
Settings: "Detected settings from url, confirm to apply?",
},
};

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const es: LocaleType = {
const es: PartialLocaleType = {
WIP: "En construcción...",
Error: {
Unauthorized:
"Acceso no autorizado, por favor ingrese el código de acceso en la página de configuración.",
"Acceso no autorizado, por favor ingrese el código de acceso en la [página](/#/auth) de configuración.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} mensajes`,
@@ -61,37 +61,21 @@ const es: LocaleType = {
Settings: {
Title: "Configuración",
SubTitle: "Todas las configuraciones",
Actions: {
ClearAll: "Borrar todos los datos",
ResetAll: "Restablecer todas las configuraciones",
Close: "Cerrar",
ConfirmResetAll: "Are you sure you want to reset all configurations?",
ConfirmClearAll: "Are you sure you want to reset all chat?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Todos los idiomas",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어"
},
},
Avatar: "Avatar",
FontSize: {
Title: "Tamaño de fuente",
SubTitle: "Ajustar el tamaño de fuente del contenido del chat",
},
InjectSystemPrompts: {
Title: "Inyectar Prompts del Sistema",
SubTitle:
"Agregar forzosamente un prompt de sistema simulado de ChatGPT al comienzo de la lista de mensajes en cada solicitud",
},
Update: {
Version: (x: string) => `Versión: ${x}`,
IsLatest: "Última versión",
@@ -108,8 +92,10 @@ const es: LocaleType = {
SubTitle: "Preview markdown in bubble",
},
Mask: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
},
Prompt: {
Disable: {
@@ -138,11 +124,7 @@ const es: LocaleType = {
SubTitle:
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
},
Token: {
Title: "Clave de API",
SubTitle: "Utiliza tu clave para ignorar el límite de código de acceso",
Placeholder: "Clave de la API de OpenAI",
},
Usage: {
Title: "Saldo de la cuenta",
SubTitle(used: any, total: any) {
@@ -152,11 +134,7 @@ const es: LocaleType = {
Check: "Comprobar de nuevo",
NoAccess: "Introduzca la clave API para comprobar el saldo",
},
AccessCode: {
Title: "Código de acceso",
SubTitle: "Control de acceso habilitado",
Placeholder: "Necesita código de acceso",
},
Model: "Modelo",
Temperature: {
Title: "Temperatura",
@@ -171,6 +149,11 @@ const es: LocaleType = {
SubTitle:
"Un valor mayor aumenta la probabilidad de hablar sobre nuevos temas",
},
FrequencyPenalty: {
Title: "Penalización de frecuencia",
SubTitle:
"Un valor mayor que disminuye la probabilidad de repetir la misma línea",
},
},
Store: {
DefaultTopic: "Nueva conversación",
@@ -199,6 +182,9 @@ const es: LocaleType = {
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Eres un asistente que",
},
Mask: {
Name: "Mask",
Page: {
@@ -243,6 +229,12 @@ const es: LocaleType = {
Create: "Create",
Edit: "Edit",
},
Exporter: {
Model: "Modelo",
Messages: "Mensajes",
Topic: "Tema",
Time: "Time",
},
};
export default es;

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const fr: LocaleType = {
const fr: PartialLocaleType = {
WIP: "Prochainement...",
Error: {
Unauthorized:
"Accès non autorisé, veuillez saisir le code d'accès dans la page des paramètres.",
"Accès non autorisé, veuillez saisir le code d'accès dans la [page](/#/auth) des paramètres.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} messages en total`,
@@ -20,6 +20,31 @@ const fr: LocaleType = {
Stop: "Arrêter",
Retry: "Réessayer",
Delete: "Supprimer",
Pin: "Épingler",
PinToastContent: "Épingler 2 messages à des messages contextuels",
PinToastAction: "Voir",
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",
clear: "Effacer le contexte",
del: "Supprimer la Conversation",
},
InputActions: {
Stop: "Stop",
ToBottom: "Au dernier",
Theme: {
auto: "Auto",
light: "Thème clair",
dark: "Thème sombre",
},
Prompt: "Instructions",
Masks: "Assistants",
Clear: "Effacer le contexte",
Settings: "Réglages",
},
Rename: "Renommer la conversation",
Typing: "En train d'écrire…",
@@ -61,32 +86,24 @@ const fr: LocaleType = {
Settings: {
Title: "Paramètres",
SubTitle: "Toutes les configurations",
Actions: {
ClearAll: "Effacer toutes les données",
ResetAll: "Réinitialiser les configurations",
Close: "Fermer",
ConfirmResetAll:
"Êtes-vous sûr de vouloir réinitialiser toutes les configurations?",
ConfirmClearAll: "Êtes-vous sûr de vouloir supprimer toutes les données?",
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?",
},
Clear: {
Title: "Supprimer 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?",
},
},
Lang: {
Name: "Language", // ATTENTION : si vous souhaitez ajouter une nouvelle traduction, ne traduisez pas cette valeur, laissez-la sous forme de `Language`
All: "Toutes les langues",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Vietnamese",
ru: "Русский",
cs: "Čeština",
ko: "한국어"
},
},
Avatar: "Avatar",
@@ -94,6 +111,15 @@ const fr: LocaleType = {
Title: "Taille des polices",
SubTitle: "Ajuste la taille de police du contenu de la conversation",
},
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",
},
InputTemplate: {
Title: "Template",
SubTitle: "Le message le plus récent sera ajouté à ce template.",
},
Update: {
Version: (x: string) => `Version : ${x}`,
IsLatest: "Dernière version",
@@ -110,9 +136,15 @@ const fr: LocaleType = {
SubTitle: "Aperçu du Markdown dans une bulle",
},
Mask: {
Title: "Écran de masque",
SubTitle:
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
Splash: {
Title: "Écran de masque",
SubTitle:
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
},
Builtin: {
Title: "Masquer Les Assistants Intégrés",
SubTitle: "Masquer les assistants intégrés par défaut",
},
},
Prompt: {
Disable: {
@@ -141,11 +173,7 @@ const fr: LocaleType = {
SubTitle:
"Comprimera si la longueur des messages non compressés dépasse cette valeur",
},
Token: {
Title: "Clé API",
SubTitle: "Utilisez votre clé pour ignorer la limite du code d'accès",
Placeholder: "Clé OpenAI API",
},
Usage: {
Title: "Solde du compte",
SubTitle(used: any, total: any) {
@@ -155,18 +183,19 @@ const fr: LocaleType = {
Check: "Vérifier",
NoAccess: "Entrez la clé API pour vérifier le solde",
},
AccessCode: {
Title: "Code d'accès",
SubTitle: "Contrôle d'accès activé",
Placeholder: "Code d'accès requis",
},
Model: "Modèle",
Temperature: {
Title: "Température",
SubTitle: "Une valeur plus élevée rendra les réponses plus aléatoires",
},
TopP: {
Title: "Top P",
SubTitle:
"Ne modifiez pas à moins que vous ne sachiez ce que vous faites",
},
MaxTokens: {
Title: "Max Tokens",
Title: "Limite de Tokens",
SubTitle: "Longueur maximale des tokens d'entrée et des tokens générés",
},
PresencePenalty: {
@@ -174,6 +203,11 @@ const fr: LocaleType = {
SubTitle:
"Une valeur plus élevée augmentera la probabilité d'introduire de nouveaux sujets",
},
FrequencyPenalty: {
Title: "Pénalité de fréquence",
SubTitle:
"Une valeur plus élevée diminuant la probabilité de répéter la même ligne",
},
},
Store: {
DefaultTopic: "Nouvelle conversation",
@@ -202,6 +236,9 @@ const fr: LocaleType = {
Plugin: {
Name: "Extension",
},
FineTuned: {
Sysmessage: "Eres un asistente que",
},
Mask: {
Name: "Masque",
Page: {
@@ -225,14 +262,28 @@ const fr: LocaleType = {
Clone: "Dupliquer",
},
Config: {
Avatar: "Avatar du bot",
Name: "Nom du bot",
Avatar: "Avatar de lassistant",
Name: "Nom de lassistant",
Sync: {
Title: "Utiliser la configuration globale",
SubTitle: "Utiliser la configuration globale dans cette conversation",
Confirm: "Voulez-vous definir votre configuration personnalisée ?",
},
HideContext: {
Title: "Masquer les invites contextuelles",
SubTitle: "Ne pas afficher les instructions contextuelles dans le chat",
},
Share: {
Title: "Partager ce masque",
SubTitle: "Générer un lien vers ce masque",
Action: "Copier le lien",
},
},
},
NewChat: {
Return: "Retour",
Skip: "Passer",
Title: "Choisir un masque",
Title: "Choisir un assitant",
SubTitle: "Discutez avec l'âme derrière le masque",
More: "En savoir plus",
NotShow: "Ne pas afficher à nouveau",
@@ -247,6 +298,12 @@ const fr: LocaleType = {
Create: "Créer",
Edit: "Éditer",
},
Exporter: {
Model: "Modèle",
Messages: "Messages",
Topic: "Sujet",
Time: "Temps",
},
};
export default fr;

382
app/locales/id.ts Normal file
View File

@@ -0,0 +1,382 @@
import { SubmitKey } from "../store/config";
import { PartialLocaleType } from "./index";
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).",
},
Auth: {
Title: "Diperlukan Kode Akses",
Tips: "Masukkan kode akses di bawah",
SubTips: "Atau masukkan kunci API OpenAI Anda",
Input: "Kode Akses",
Confirm: "Konfirmasi",
Later: "Nanti",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} pesan`,
},
Chat: {
SubTitle: (count: number) => `${count} pesan`,
Actions: {
ChatList: "Buka Daftar Chat",
CompressedHistory: "Ekspor Riwayat Terkompresi",
Export: "Ekspor Semua Pesan sebagai Markdown",
Copy: "Salin",
Stop: "Berhenti",
Retry: "Coba Lagi",
Pin: "Pin",
PinToastContent: "2 pesan telah ditandai",
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",
},
InputActions: {
Stop: "Berhenti",
ToBottom: "Ke Bagian Bawah",
Theme: {
auto: "Otomatis",
light: "Tema Terang",
dark: "Tema Gelap",
},
Prompt: "Prompts",
Masks: "Masks",
Clear: "Bersihkan Percakapan",
Settings: "Pengaturan",
},
Rename: "Ubah Nama Chat",
Typing: "Mengetik...",
Input: (submitKey: string) => {
var inputHints = `${submitKey} untuk mengirim`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter untuk membalut";
}
return (
inputHints + ", / untuk mencari prompt, : untuk menggunakan perintah"
);
},
Send: "Kirim",
Config: {
Reset: "Reset ke Default",
SaveAs: "Simpan sebagai Masks",
},
},
Export: {
Title: "Ekspor Pesan",
Copy: "Salin Semua",
Download: "Unduh",
MessageFromYou: "Pesan dari Anda",
MessageFromChatGPT: "Pesan dari ChatGPT",
Share: "Bagikan ke ShareGPT",
Format: {
Title: "Format Ekspor",
SubTitle: "Markdown atau Gambar PNG",
},
IncludeContext: {
Title: "Sertakan Konteks",
SubTitle: "Apakah akan menyertakan masks",
},
Steps: {
Select: "Pilih",
Preview: "Pratinjau",
},
},
Select: {
Search: "Cari",
All: "Pilih Semua",
Latest: "Pilih Terbaru",
Clear: "Bersihkan",
},
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?",
},
Home: {
NewChat: "Obrolan Baru",
DeleteChat: "Anda yakin ingin menghapus percakapan yang dipilih?",
DeleteToast: "Percakapan telah dihapus",
Revert: "Kembali",
},
Settings: {
Title: "Pengaturan",
SubTitle: "Semua 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?",
},
Clear: {
Title: "Hapus Semua Data",
SubTitle: "Semua data yang tersimpan secara lokal akan dihapus",
Action: "Hapus",
Confirm:
"Apakah Anda yakin ingin menghapus semua data yang tersimpan secara lokal?",
},
},
Lang: {
Name: "Bahasa", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Semua Bahasa",
},
Avatar: "Avatar",
FontSize: {
Title: "Ukuran Font",
SubTitle: "Ubah ukuran font konten chat",
},
InjectSystemPrompts: {
Title: "Suntikkan Petunjuk Sistem",
SubTitle:
"Tambahkan petunjuk simulasi sistem ChatGPT di awal daftar pesan yang diminta dalam setiap permintaan",
},
InputTemplate: {
Title: "Template Input",
SubTitle: "Pesan baru akan diisi menggunakan template ini",
},
Update: {
Version: (x: string) => `Version: ${x}`,
IsLatest: "Versi terbaru",
CheckUpdate: "Periksa Pembaruan",
IsChecking: "Memeriksa pembaruan...",
FoundUpdate: (x: string) => `Versi terbaru ditemukan: ${x}`,
GoToUpdate: "Perbarui Sekarang",
},
AutoGenerateTitle: {
Title: "Hasilkan Judul Otomatis",
SubTitle: "Hasilkan judul yang sesuai berdasarkan konten percakapan",
},
Sync: {
CloudState: "Pembaruan Terakhir",
NotSyncYet: "Belum disinkronkan",
Success: "Sinkronisasi Berhasil",
Fail: "Sinkronisasi Gagal",
Config: {
Modal: {
Title: "Konfigurasi Sinkronisasi",
},
SyncType: {
Title: "Tipe Sinkronisasi",
SubTitle: "Pilih layanan sinkronisasi favorit Anda",
},
Proxy: {
Title: "Aktifkan Proxy CORS",
SubTitle:
"Aktifkan Proxy untuk menghindari pembatasan atau pemblokiran lintas sumber",
},
ProxyUrl: {
Title: "Lokasi Titik Akhir Proxy CORS",
SubTitle: "Hanya berlaku untuk Proxy CORS bawaan untuk proyek ini",
},
WebDav: {
Endpoint: "Lokasi Titik Akhir WebDAV",
UserName: "User Pengguna",
Password: "Kata Sandi",
},
},
},
SendKey: "Kirim",
Theme: "Tema",
TightBorder: "Batas Ketat",
SendPreviewBubble: {
Title: "Pratinjau Obrolan",
SubTitle: "Pratinjau Obrolan dengan markdown",
},
Mask: {
Splash: {
Title: "Layar Pembuka Masks",
SubTitle:
"Tampilkan layar pembuka masks sebelum memulai percakapan baru",
},
Builtin: {
Title: "Sembunyikan Masks Bawaan",
SubTitle: "Sembunyikan Masks bawaan dari daftar masks",
},
},
Prompt: {
Disable: {
Title: "Nonaktifkan Otomatisasi",
SubTitle: "Aktifkan/Matikan otomatisasi",
},
List: "Daftar Prompt",
ListCount: (builtin: number, custom: number) =>
`${builtin} bawaan, ${custom} penggunaan khusus`,
Edit: "Edit",
Modal: {
Title: "Daftar Prompt",
Add: "Tambahkan",
Search: "Cari Prompt",
},
EditModal: {
Title: "Edit Prompt",
},
},
HistoryCount: {
Title: "Jumlah Pesan Riwayat",
SubTitle: "Jumlah pesan yang akan dikirim setiap permintaan",
},
CompressThreshold: {
Title: "Batas Kompresi Riwayat",
SubTitle:
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
},
Usage: {
Title: "Saldo Akun",
SubTitle(used: any, total: any) {
return `Digunakan bulan ini: ${used}, total langganan: ${total}`;
},
IsChecking: "Memeriksa...",
Check: "Periksa",
NoAccess: "Masukkan kunci API untuk memeriksa saldo",
},
Model: "Model",
Temperature: {
Title: "Suhu",
SubTitle: "Semakin tinggi nilainya, semakin acak keluarannya",
},
TopP: {
Title: "Top P",
SubTitle: "Tidak mengubah nilai dengan suhu",
},
MaxTokens: {
Title: "Token Maksimum",
SubTitle: "Panjang maksimum token input dan output",
},
PresencePenalty: {
Title: "Penalti Kehadiran",
SubTitle: "Semakin tinggi nilai, semakin mungkin topik baru muncul",
},
FrequencyPenalty: {
Title: "Penalti Frekuensi",
SubTitle:
"Semakin tinggi nilai, semakin rendah kemungkinan penggunaan ulang baris yang sama",
},
},
Store: {
DefaultTopic: "Percakapan Baru",
BotHello: "Halo! Bagaimana saya bisa membantu Anda hari ini?",
Error: "Terjadi kesalahan, silakan coba lagi nanti.",
Prompt: {
History: (content: string) =>
"Ini adalah ringkasan singkat dari riwayat percakapan: " + 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.",
Summarize:
"Buat ringkasan percakapan dalam 200 kata yang akan digunakan sebagai promp di masa depan.",
},
},
Copy: {
Success: "Tersalin ke clipboard",
Failed:
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
},
Download: {
Success: "Konten berhasil diunduh ke direktori Anda.",
Failed: "Unduhan gagal.",
},
Context: {
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
Edit: "Pengaturan Obrolan Saat Ini",
Add: "Tambahkan Promp",
Clear: "Bersihkan Konteks",
Revert: "Kembali ke Posisi Sebelumnya",
},
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Anda adalah asisten yang",
},
Mask: {
Name: "Masks",
Page: {
Title: "Template Promp",
SubTitle: (count: number) => `${count} template prompt`,
Search: "Cari template",
Create: "Buat",
},
Item: {
Info: (count: number) => `${count} prompt`,
Chat: "Obrolan",
View: "Lihat",
Edit: "Edit",
Delete: "Hapus",
DeleteConfirm: "Anda yakin ingin menghapus?",
},
EditModal: {
Title: (readonly: boolean) =>
`Edit Template Prompt ${readonly ? "(hanya baca)" : ""}`,
Download: "Unduh",
Clone: "Duplikat",
},
Config: {
Avatar: "Avatar Bot",
Name: "Nama Bot",
Sync: {
Title: "Gunakan Konfigurasi Global",
SubTitle: "Gunakan konfigurasi global dalam percakapan ini",
Confirm:
"Pastikan untuk mengganti konfigurasi kustom dengan konfigurasi global?",
},
HideContext: {
Title: "Sembunyikan Prompt Konteks",
SubTitle: "Tidak menampilkan prompt konteks dalam obrolan",
},
Share: {
Title: "Bagikan Masks Ini",
SubTitle: "Buat tautan untuk masks 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",
ConfirmNoShow:
"Pastikan untuk menonaktifkannya? Anda dapat mengaktifkannya nanti melalui pengaturan.",
},
UI: {
Confirm: "Konfirmasi",
Cancel: "Batal",
Close: "Tutup",
Create: "Buat",
Edit: "Edit",
},
Exporter: {
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 ?",
},
};
export default id;

View File

@@ -1,40 +1,80 @@
import CN from "./cn";
import EN from "./en";
import TW from "./tw";
import FR from "./fr";
import ES from "./es";
import IT from "./it";
import TR from "./tr";
import JP from "./jp";
import DE from "./de";
import VI from "./vi";
import RU from "./ru";
import CS from "./cs";
import KO from "./ko";
import cn from "./cn";
import en from "./en";
import tw from "./tw";
import id from "./id";
import fr from "./fr";
import es from "./es";
import it from "./it";
import tr from "./tr";
import jp from "./jp";
import de from "./de";
import vi from "./vi";
import ru from "./ru";
import no from "./no";
import cs from "./cs";
import ko from "./ko";
import ar from "./ar";
import bn from "./bn";
import { merge } from "../utils/merge";
export type { LocaleType } from "./cn";
import type { LocaleType } from "./cn";
export type { LocaleType, PartialLocaleType } from "./cn";
export const AllLangs = [
"en",
"cn",
"tw",
"fr",
"es",
"it",
"tr",
"jp",
"de",
"vi",
"ru",
"cs",
"ko",
] as const;
export type Lang = (typeof AllLangs)[number];
const ALL_LANGS = {
cn,
en,
tw,
jp,
ko,
id,
fr,
es,
it,
tr,
de,
vi,
ru,
cs,
no,
ar,
bn,
};
export type Lang = keyof typeof ALL_LANGS;
export const AllLangs = Object.keys(ALL_LANGS) as Lang[];
export const ALL_LANG_OPTIONS: Record<Lang, string> = {
cn: "简体中文",
en: "English",
tw: "繁體中文",
jp: "日本語",
ko: "한국어",
id: "Indonesia",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
no: "Nynorsk",
ar: "العربية",
bn: "বাংলা",
};
const LANG_KEY = "lang";
const DEFAULT_LANG = "en";
const fallbackLang = en;
const targetLang = ALL_LANGS[getLang()] as LocaleType;
// if target lang missing some fields, it will use fallback lang string
merge(fallbackLang, targetLang);
export default fallbackLang as LocaleType;
function getItem(key: string) {
try {
return localStorage.getItem(key);
@@ -53,7 +93,6 @@ function getLanguage() {
try {
return navigator.language.toLowerCase();
} catch {
console.log("[Lang] failed to detect user lang.");
return DEFAULT_LANG;
}
}
@@ -81,24 +120,12 @@ export function changeLang(lang: Lang) {
location.reload();
}
const fallbackLang = EN;
const targetLang = {
en: EN,
cn: CN,
tw: TW,
fr: FR,
es: ES,
it: IT,
tr: TR,
jp: JP,
de: DE,
vi: VI,
ru: RU,
cs: CS,
ko: KO,
}[getLang()] as typeof CN;
export function getISOLang() {
const isoLangString: Record<string, string> = {
cn: "zh-Hans",
tw: "zh-Hant",
};
// if target lang missing some fields, it will use fallback lang string
merge(fallbackLang, targetLang);
export default fallbackLang as typeof CN;
const lang = getLang();
return isoLangString[lang] ?? lang;
}

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const it: LocaleType = {
const it: PartialLocaleType = {
WIP: "Work in progress...",
Error: {
Unauthorized:
"Accesso non autorizzato, inserire il codice di accesso nella pagina delle impostazioni.",
"Accesso non autorizzato, inserire il codice di accesso nella [pagina](/#/auth) delle impostazioni.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} messaggi`,
@@ -61,37 +61,21 @@ const it: LocaleType = {
Settings: {
Title: "Impostazioni",
SubTitle: "Tutte le impostazioni",
Actions: {
ClearAll: "Cancella tutti i dati",
ResetAll: "Resetta tutte le impostazioni",
Close: "Chiudi",
ConfirmResetAll: "Sei sicuro vuoi cancellare tutte le impostazioni?",
ConfirmClearAll: "Sei sicuro vuoi cancellare tutte le chat?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Tutte le lingue",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "Avatar",
FontSize: {
Title: "Dimensione carattere",
SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat",
},
InjectSystemPrompts: {
Title: "Inserisci Prompts di Sistema",
SubTitle:
"Aggiungi forzatamente un prompt di sistema simulato di ChatGPT all'inizio della lista dei messaggi per ogni richiesta",
},
Update: {
Version: (x: string) => `Versione: ${x}`,
IsLatest: "Ultima versione",
@@ -108,8 +92,10 @@ const it: LocaleType = {
SubTitle: "Preview markdown in bubble",
},
Mask: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
Splash: {
Title: "Mask Splash Screen",
SubTitle: "Show a mask splash screen before starting new chat",
},
},
Prompt: {
Disable: {
@@ -138,12 +124,7 @@ const it: LocaleType = {
SubTitle:
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
},
Token: {
Title: "API Key",
SubTitle:
"Utilizzare la chiave per ignorare il limite del codice di accesso",
Placeholder: "OpenAI API Key",
},
Usage: {
Title: "Bilancio Account",
SubTitle(used: any, total: any) {
@@ -153,11 +134,7 @@ const it: LocaleType = {
Check: "Controlla ancora",
NoAccess: "Inserire la chiave API per controllare il saldo",
},
AccessCode: {
Title: "Codice d'accesso",
SubTitle: "Controllo d'accesso abilitato",
Placeholder: "Inserisci il codice d'accesso",
},
Model: "Modello GPT",
Temperature: {
Title: "Temperature",
@@ -172,6 +149,11 @@ const it: LocaleType = {
SubTitle:
"Un valore maggiore aumenta la probabilità di parlare di nuovi argomenti",
},
FrequencyPenalty: {
Title: "Penalità di frequenza",
SubTitle:
"Un valore maggiore che diminuisce la probabilità di ripetere la stessa riga",
},
},
Store: {
DefaultTopic: "Nuova conversazione",
@@ -200,6 +182,9 @@ const it: LocaleType = {
Plugin: {
Name: "Plugin",
},
FineTuned: {
Sysmessage: "Sei un assistente che",
},
Mask: {
Name: "Mask",
Page: {
@@ -244,6 +229,12 @@ const it: LocaleType = {
Create: "Create",
Edit: "Edit",
},
Exporter: {
Model: "Modello",
Messages: "Messaggi",
Topic: "Argomento",
Time: "Tempo",
},
};
export default it;

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const jp: LocaleType = {
WIP: "この機能は開発中です……",
const jp: PartialLocaleType = {
WIP: "この機能は開発中です",
Error: {
Unauthorized:
"現在は未承認状態です。左下の設定ボタンをクリックし、アクセスパスワードを入力してください。",
"現在は未承認状態です。左下の設定ボタンをクリックし、アクセスパスワードかOpenAIのAPIキーを入力してください。",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} 通のチャット`,
@@ -19,7 +19,12 @@ const jp: LocaleType = {
Copy: "コピー",
Stop: "停止",
Retry: "リトライ",
Delete: "Delete",
Pin: "ピン",
PinToastContent:
"コンテキストプロンプトに1つのメッセージをピン留めしました",
PinToastAction: "表示",
Delete: "削除",
Edit: "編集",
},
Rename: "チャットの名前を変更",
Typing: "入力中…",
@@ -32,8 +37,8 @@ const jp: LocaleType = {
},
Send: "送信",
Config: {
Reset: "重置默认",
SaveAs: "另存为面具",
Reset: "リセット",
SaveAs: "保存",
},
},
Export: {
@@ -61,38 +66,38 @@ const jp: LocaleType = {
Settings: {
Title: "設定",
SubTitle: "設定オプション",
Actions: {
ClearAll: "すべてのデータをクリア",
ResetAll: "すべてのオプションをリセット",
Close: "閉じる",
ConfirmResetAll: "すべての設定をリセットしてもよろしいですか?",
ConfirmClearAll: "すべてのチャットをリセットしてもよろしいですか?",
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: "所有语言",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어"
},
All: "全ての言語",
},
Avatar: "アバター",
FontSize: {
Title: "フォントサイズ",
SubTitle: "チャット内容のフォントサイズ",
},
InjectSystemPrompts: {
Title: "システムプロンプトの挿入",
SubTitle:
"各リクエストのメッセージリストの先頭に、ChatGPTのシステムプロンプトを強制的に追加します",
},
InputTemplate: {
Title: "入力の前処理",
SubTitle: "新規入力がこのテンプレートに埋め込まれます",
},
Update: {
Version: (x: string) => `現在のバージョン:${x}`,
IsLatest: "最新バージョンです",
@@ -106,11 +111,13 @@ const jp: LocaleType = {
TightBorder: "ボーダーレスモード",
SendPreviewBubble: {
Title: "プレビューバブルの送信",
SubTitle: "在预览气泡中预览 Markdown 内容",
SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー",
},
Mask: {
Title: "面具启动页",
SubTitle: "新建聊天时,展示面具启动页",
Splash: {
Title: "キャラクターページ",
SubTitle: "新規チャット作成時にキャラクターページを表示する",
},
},
Prompt: {
Disable: {
@@ -128,7 +135,7 @@ const jp: LocaleType = {
Search: "プロンプトワード検索",
},
EditModal: {
Title: "编辑提示词",
Title: "編集",
},
},
HistoryCount: {
@@ -140,11 +147,7 @@ const jp: LocaleType = {
SubTitle:
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
},
Token: {
Title: "APIキー",
SubTitle: "自分のキーを使用してパスワードアクセス制限を迂回する",
Placeholder: "OpenAI APIキー",
},
Usage: {
Title: "残高照会",
SubTitle(used: any, total: any) {
@@ -154,11 +157,7 @@ const jp: LocaleType = {
Check: "再確認",
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
},
AccessCode: {
Title: "アクセスパスワード",
SubTitle: "暗号化アクセスが有効になっています",
Placeholder: "アクセスパスワードを入力してください",
},
Model: "モデル (model)",
Temperature: {
Title: "ランダム性 (temperature)",
@@ -173,6 +172,10 @@ const jp: LocaleType = {
Title: "トピックの新鮮度 (presence_penalty)",
SubTitle: "値が大きいほど、新しいトピックへの展開が可能になります。",
},
FrequencyPenalty: {
Title: "話題の頻度 (frequency_penalty)",
SubTitle: "値が大きいほど、重複語を低減する可能性が高くなります",
},
},
Store: {
DefaultTopic: "新しいチャット",
@@ -193,54 +196,71 @@ const jp: LocaleType = {
Failed: "コピーに失敗しました。クリップボード許可を与えてください。",
},
Context: {
Toast: (x: any) => `前置コンテキスト${x} 件設定されました`,
Edit: "前置コンテキストと履歴メモリ",
Add: "新規追加",
Toast: (x: any) => `キャラクター${x} 件設定されました`,
Edit: "キャラクタープリセットとモデル設定",
Add: "追加",
},
Plugin: { Name: "插件" },
Plugin: { Name: "プラグイン" },
FineTuned: { Sysmessage: "あなたはアシスタントです" },
Mask: {
Name: "面具",
Name: "キャラクタープリセット",
Page: {
Title: "预设角色面具",
SubTitle: (count: number) => `${count} 个预设角色定义`,
Search: "搜索角色面具",
Create: "新",
Title: "キャラクタープリセット",
SubTitle: (count: number) => `${count} 件見つかりました。`,
Search: "検索",
Create: "新",
},
Item: {
Info: (count: number) => `包含 ${count} 条预设对话`,
Chat: "对话",
View: "查看",
Edit: "编辑",
Delete: "除",
DeleteConfirm: "确认删除",
Chat: "会話",
View: "詳細",
Edit: "編集",
Delete: "除",
DeleteConfirm: "本当に削除しますか",
},
EditModal: {
Title: (readonly: boolean) =>
`编辑预设面具 ${readonly ? "只读" : ""}`,
Download: "下载预设",
Clone: "克隆预设",
`キャラクタープリセットを編集 ${readonly ? "読み取り専用" : ""}`,
Download: "ダウンロード",
Clone: "複製",
},
Config: {
Avatar: "角色头像",
Name: "角色名称",
Avatar: "キャラクターのアイコン",
Name: "キャラクターの名前",
Sync: {
Title: "グローバル設定を利用する",
SubTitle: "このチャットでグローバル設定を利用します。",
Confirm:
"カスタム設定を上書きしてグローバル設定を使用します、よろしいですか?",
},
HideContext: {
Title: "キャラクター設定を表示しない",
SubTitle: "チャット画面でのキャラクター設定を非表示にします。",
},
},
},
NewChat: {
Return: "返回",
Skip: "跳过",
Title: "挑选一个面具",
SubTitle: "现在开始,与面具背后的灵魂思维碰撞",
More: "搜索更多",
NotShow: "不再展示",
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
Return: "戻る",
Skip: "スキップ",
Title: "キャラクター",
SubTitle: "さあ、AIにキャラクターを設定して会話を始めてみましょう",
More: "もっと探す",
NotShow: "今後は表示しない",
ConfirmNoShow: "いつでも設定から有効化できます。",
},
UI: {
Confirm: "确认",
Cancel: "取消",
Close: "关闭",
Create: "新",
Edit: "编辑",
Confirm: "確認",
Cancel: "キャンセル",
Close: "閉じる",
Create: "新",
Edit: "編集",
},
Exporter: {
Model: "モデル",
Messages: "メッセージ",
Topic: "トピック",
Time: "時間",
},
};

View File

@@ -1,11 +1,12 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const ko: LocaleType = {
const ko: PartialLocaleType = {
WIP: "곧 출시 예정...",
Error: {
Unauthorized: "권한이 없습니다. 설정 페이지에서 액세스 코드를 입력하세요.",
Unauthorized:
"권한이 없습니다. 설정 페이지에서 액세스 코드를 [입력하세요](/#/auth).",
},
ChatItem: {
ChatItemCount: (count: number) => `${count}개의 메시지`,
@@ -61,37 +62,21 @@ const ko: LocaleType = {
Settings: {
Title: "설정",
SubTitle: "모든 설정",
Actions: {
ClearAll: "모든 데이터 지우기",
ResetAll: "모든 설정 초기화",
Close: "닫기",
ConfirmResetAll: "모든 설정을 초기화하시겠습니까?",
ConfirmClearAll: "모든 데이터를 지우시겠습니까?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "All Languages",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "아바타",
FontSize: {
Title: "글꼴 크기",
SubTitle: "채팅 내용의 글꼴 크기 조정",
},
Title: "글꼴 크기",
SubTitle: "채팅 내용의 글꼴 크기 조정",
},
InjectSystemPrompts: {
Title: "시스템 프롬프트 주입",
SubTitle:
"각 요청의 메시지 목록의 시작에 ChatGPT 시스템 프롬프트를 강제로 추가합니다",
},
Update: {
Version: (x: string) => `버전: ${x}`,
IsLatest: "최신 버전",
@@ -108,8 +93,10 @@ const ko: LocaleType = {
SubTitle: "버블에서 마크다운 미리 보기",
},
Mask: {
Title: "마스크 시작 화면",
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
Splash: {
Title: "마스크 시작 화면",
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
},
},
Prompt: {
Disable: {
@@ -135,14 +122,9 @@ const ko: LocaleType = {
},
CompressThreshold: {
Title: "기록 압축 임계값",
SubTitle:
"미압축 메시지 길이가 임계값을 초과하면 압축됨",
},
Token: {
Title: "API 키",
SubTitle: "액세스 코드 제한을 무시하기 위해 키 사용",
Placeholder: "OpenAI API 키",
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
},
Usage: {
Title: "계정 잔액",
SubTitle(used: any, total: any) {
@@ -152,11 +134,7 @@ const ko: LocaleType = {
Check: "확인",
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
},
AccessCode: {
Title: "액세스 코드",
SubTitle: "액세스 제어가 활성화됨",
Placeholder: "액세스 코드 입력",
},
Model: "모델",
Temperature: {
Title: "온도 (temperature)",
@@ -165,11 +143,14 @@ const ko: LocaleType = {
MaxTokens: {
Title: "최대 토큰 수 (max_tokens)",
SubTitle: "입력 토큰과 생성된 토큰의 최대 길이",
},
},
PresencePenalty: {
Title: "존재 페널티 (presence_penalty)",
SubTitle:
"값이 클수록 새로운 주제에 대해 대화할 가능성이 높아집니다.",
SubTitle: "값이 클수록 새로운 주제에 대해 대화할 가능성이 높아집니다.",
},
FrequencyPenalty: {
Title: "빈도 페널티(frequency penalty)",
SubTitle: "값이 클수록 같은 줄이 반복될 가능성이 줄어듭니다.",
},
},
Store: {
@@ -178,8 +159,7 @@ const ko: LocaleType = {
Error: "문제가 발생했습니다. 나중에 다시 시도해주세요.",
Prompt: {
History: (content: string) =>
"이것은 AI와 사용자 간의 대화 기록을 요약한 내용입니다: " +
content,
"이것은 AI와 사용자 간의 대화 기록을 요약한 내용입니다: " + content,
Topic:
"다음과 같이 대화 내용을 요약하는 4~5단어 제목을 생성해주세요. 따옴표, 구두점, 인용부호, 기호 또는 추가 텍스트를 제거하십시오. 따옴표로 감싸진 부분을 제거하십시오.",
Summarize:
@@ -198,6 +178,9 @@ const ko: LocaleType = {
Plugin: {
Name: "플러그인",
},
FineTuned: {
Sysmessage: "당신은 어시스턴트입니다",
},
Mask: {
Name: "마스크",
Page: {
@@ -232,7 +215,8 @@ const ko: LocaleType = {
SubTitle: "마스크 뒤의 영혼과 대화하세요",
More: "더 보기",
NotShow: "다시 표시하지 않음",
ConfirmNoShow: "비활성화하시겠습니까? 나중에 설정에서 다시 활성화할 수 있습니다.",
ConfirmNoShow:
"비활성화하시겠습니까? 나중에 설정에서 다시 활성화할 수 있습니다.",
},
UI: {
@@ -242,6 +226,12 @@ const ko: LocaleType = {
Create: "생성",
Edit: "편집",
},
};
Exporter: {
Model: "모델",
Messages: "메시지",
Topic: "주제",
Time: "시간",
},
};
export default ko;

161
app/locales/no.ts Normal file
View File

@@ -0,0 +1,161 @@
import { SubmitKey } from "../store/config";
import type { PartialLocaleType } from "./index";
const no: PartialLocaleType = {
WIP: "Arbeid pågår ...",
Error: {
Unauthorized:
"Du har ikke tilgang. [Vennlig oppgi tildelt adgangskode](/#/auth).",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} meldinger`,
},
Chat: {
SubTitle: (count: number) => `${count} meldinger med ChatGPT`,
Actions: {
ChatList: "Gå til chatlisten",
CompressedHistory: "Komprimert historikk for instrukser",
Export: "Eksporter alle meldinger i markdown-format",
Copy: "Kopier",
Stop: "Stopp",
Retry: "Prøv igjen",
Delete: "Slett",
},
Rename: "Gi nytt navn",
Typing: "Skriver …",
Input: (submitKey: string) => {
var inputHints = `${submitKey} for å sende`;
if (submitKey === String(SubmitKey.Enter)) {
inputHints += ", Shift + Enter for å omgi";
}
return inputHints + ", / for å søke instrukser";
},
Send: "Send",
},
Export: {
Title: "Alle meldinger",
Copy: "Kopiere alle",
Download: "Last ned",
MessageFromYou: "Melding fra deg",
MessageFromChatGPT: "Melding fra ChatGPT",
},
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?",
},
Home: {
NewChat: "Ny chat",
DeleteChat: "Bekreft for å slette det valgte dialogen",
DeleteToast: "Samtale slettet",
Revert: "Tilbakestill",
},
Settings: {
Title: "Innstillinger",
SubTitle: "Alle innstillinger",
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
},
Avatar: "Avatar",
FontSize: {
Title: "Fontstørrelsen",
SubTitle: "Juster fontstørrelsen for samtaleinnholdet.",
},
InjectSystemPrompts: {
Title: "Sett inn systemprompter",
SubTitle:
"Tving tillegg av en simulert ChatGPT-systemprompt i begynnelsen av meldingslisten for hver forespørsel",
},
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",
},
SendKey: "Send nøkkel",
Theme: "Tema",
TightBorder: "Stram innramming",
Prompt: {
Disable: {
Title: "Skru av autofullfør",
SubTitle: "Skriv / for å trigge autofullfør",
},
List: "Instruksliste",
ListCount: (builtin: number, custom: number) =>
`${builtin} innebygde, ${custom} brukerdefinerte`,
Edit: "Endre",
Modal: {
Title: "Instruksliste",
Add: "Legg til",
Search: "Søk instrukser",
},
},
HistoryCount: {
Title: "Tall på tilhørende meldinger",
SubTitle: "Antall sendte meldinger tilknyttet hver spørring",
},
CompressThreshold: {
Title: "Terskeverdi for komprimering av historikk",
SubTitle:
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
},
Usage: {
Title: "Saldo for konto",
SubTitle(used: any, total: any) {
return `Brukt denne måneden $${used}, abonnement $${total}`;
},
IsChecking: "Sjekker ...",
Check: "Sjekk",
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
},
Model: "Model",
Temperature: {
Title: "Temperatur",
SubTitle: "Høyere verdi gir mer kreative svar",
},
MaxTokens: {
Title: "Maks tokens",
SubTitle: "Maksimum lengde på tokens for instrukser og svar",
},
},
Store: {
DefaultTopic: "Ny samtale",
BotHello: "Hei! Hva kan jeg hjelpe deg med i dag?",
Error: "Noe gikk galt, vennligst prøv igjen senere.",
Prompt: {
History: (content: string) =>
"Dette er et sammendrag av chatthistorikken mellom AI-en og brukeren som en oppsummering: " +
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.",
Summarize:
"Oppsummer diskusjonen vår kort i 200 ord eller mindre for å bruke som en oppfordring til fremtidig sammenheng.",
},
},
Copy: {
Success: "Kopiert til utklippstavle",
Failed: "Kopiering feilet. Vennligst gi tilgang til utklippstavlen.",
},
Context: {
Toast: (x: any) => `Med ${x} kontekstuelle instrukser`,
Edit: "Kontekstuelle -og minneinstrukser",
Add: "Legg til",
},
Exporter: {
Model: "Model",
Messages: "Meldingar",
Topic: "Emne",
Time: "Tid",
},
};
export default no;

View File

@@ -1,11 +1,11 @@
import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index";
import type { PartialLocaleType } from "./index";
const ru: LocaleType = {
const ru: PartialLocaleType = {
WIP: "Скоро...",
Error: {
Unauthorized:
"Несанкционированный доступ. Пожалуйста, введите код доступа на странице настроек.",
"Несанкционированный доступ. Пожалуйста, введите код доступа на [странице](/#/auth) настроек.",
},
ChatItem: {
ChatItemCount: (count: number) => `${count} сообщений`,
@@ -61,37 +61,21 @@ const ru: LocaleType = {
Settings: {
Title: "Настройки",
SubTitle: "Все настройки",
Actions: {
ClearAll: "Очистить все данные",
ResetAll: "Сбросить все настройки",
Close: "Закрыть",
ConfirmResetAll: "Вы уверены, что хотите сбросить все настройки?",
ConfirmClearAll: "Вы уверены, что хотите очистить все данные?",
},
Lang: {
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
All: "Все языки",
Options: {
cn: "简体中文",
en: "English",
tw: "繁體中文",
fr: "Français",
es: "Español",
it: "Italiano",
tr: "Türkçe",
jp: "日本語",
de: "Deutsch",
vi: "Tiếng Việt",
ru: "Русский",
cs: "Čeština",
ko: "한국어",
},
},
Avatar: "Аватар",
FontSize: {
Title: "Размер шрифта",
SubTitle: "Настроить размер шрифта контента чата",
},
InjectSystemPrompts: {
Title: "Вставить системные подсказки",
SubTitle:
"Принудительно добавить симулированную системную подсказку ChatGPT в начало списка сообщений для каждого запроса",
},
Update: {
Version: (x: string) => `Версия: ${x}`,
IsLatest: "Последняя версия",
@@ -108,8 +92,10 @@ const ru: LocaleType = {
SubTitle: "Предварительный просмотр markdown в пузыре",
},
Mask: {
Title: "Экран заставки маски",
SubTitle: "Показывать экран заставки маски перед началом нового чата",
Splash: {
Title: "Экран заставки маски",
SubTitle: "Показывать экран заставки маски перед началом нового чата",
},
},
Prompt: {
Disable: {
@@ -139,11 +125,7 @@ const ru: LocaleType = {
SubTitle:
"Будет сжимать, если длина несжатых сообщений превышает указанное значение",
},
Token: {
Title: "API ключ",
SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа",
Placeholder: "API ключ OpenAI",
},
Usage: {
Title: "Баланс аккаунта",
SubTitle(used: any, total: any) {
@@ -153,11 +135,7 @@ const ru: LocaleType = {
Check: "Проверить",
NoAccess: "Введите API ключ, чтобы проверить баланс",
},
AccessCode: {
Title: "Код доступа",
SubTitle: "Контроль доступа включен",
Placeholder: "Требуется код доступа",
},
Model: "Модель",
Temperature: {
Title: "Температура",
@@ -172,6 +150,11 @@ const ru: LocaleType = {
SubTitle:
"Чем выше значение, тем больше вероятность общения на новые темы",
},
FrequencyPenalty: {
Title: "Штраф за частоту",
SubTitle:
"Большее значение снижает вероятность повторения одной и той же строки",
},
},
Store: {
DefaultTopic: "Новый разговор",
@@ -200,6 +183,9 @@ const ru: LocaleType = {
Plugin: {
Name: "Плагин",
},
FineTuned: {
Sysmessage: "Вы - ассистент, который",
},
Mask: {
Name: "Маска",
Page: {
@@ -247,6 +233,12 @@ const ru: LocaleType = {
Create: "Создать",
Edit: "Редактировать",
},
Exporter: {
Model: "Модель",
Messages: "Сообщения",
Topic: "Тема",
Time: "Время",
},
};
export default ru;

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