Upload 89 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .eslintignore +1 -0
- .eslintrc.json +4 -0
- .github/workflows/docker.yml +52 -0
- .github/workflows/sync.yml +16 -0
- .gitignore +39 -0
- .gitpod.yml +11 -0
- .husky/pre-commit +4 -0
- .lintstagedrc.json +6 -0
- .prettierrc.js +10 -0
- .vscode/settings.json +4 -0
- 404.html +1632 -0
- Dockerfile +40 -0
- LICENSE +75 -0
- README.md +204 -0
- README.txt +22 -0
- app/api/access.ts +17 -0
- app/api/chat-stream/route.ts +52 -0
- app/api/common.ts +22 -0
- app/api/openai/route.ts +28 -0
- app/api/openai/typing.ts +7 -0
- app/components/button.module.scss +60 -0
- app/components/button.tsx +28 -0
- app/components/home.module.scss +456 -0
- app/components/home.tsx +707 -0
- app/components/markdown.tsx +42 -0
- app/components/settings.module.scss +20 -0
- app/components/settings.tsx +486 -0
- app/components/ui-lib.module.scss +160 -0
- app/components/ui-lib.tsx +142 -0
- app/components/window.scss +35 -0
- app/constant.ts +6 -0
- app/icons/add.svg +23 -0
- app/icons/bot.svg +28 -0
- app/icons/brain.svg +25 -0
- app/icons/chat.svg +27 -0
- app/icons/chatgpt.svg +16 -0
- app/icons/clear.svg +1 -0
- app/icons/close.svg +21 -0
- app/icons/copy.svg +1 -0
- app/icons/delete.svg +12 -0
- app/icons/download.svg +1 -0
- app/icons/edit.svg +1 -0
- app/icons/export.svg +1 -0
- app/icons/github.svg +29 -0
- app/icons/menu.svg +25 -0
- app/icons/reload.svg +24 -0
- app/icons/send-white.svg +21 -0
- app/icons/settings.svg +21 -0
- app/icons/three-dots.svg +33 -0
- app/icons/user.svg +34 -0
.eslintignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
public/serviceWorker.js
|
.eslintrc.json
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": "next/core-web-vitals",
|
3 |
+
"plugins": ["prettier"]
|
4 |
+
}
|
.github/workflows/docker.yml
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Publish Docker image
|
2 |
+
|
3 |
+
on:
|
4 |
+
workflow_dispatch:
|
5 |
+
release:
|
6 |
+
types: [published]
|
7 |
+
|
8 |
+
jobs:
|
9 |
+
push_to_registry:
|
10 |
+
name: Push Docker image to Docker Hub
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
-
|
14 |
+
name: Check out the repo
|
15 |
+
uses: actions/checkout@v3
|
16 |
+
-
|
17 |
+
name: Log in to Docker Hub
|
18 |
+
uses: docker/login-action@v2
|
19 |
+
with:
|
20 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
21 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
22 |
+
|
23 |
+
-
|
24 |
+
name: Extract metadata (tags, labels) for Docker
|
25 |
+
id: meta
|
26 |
+
uses: docker/metadata-action@v4
|
27 |
+
with:
|
28 |
+
images: yidadaa/chatgpt-next-web
|
29 |
+
tags: |
|
30 |
+
type=raw,value=latest
|
31 |
+
type=semver,pattern={{version}}
|
32 |
+
|
33 |
+
-
|
34 |
+
name: Set up QEMU
|
35 |
+
uses: docker/setup-qemu-action@v2
|
36 |
+
|
37 |
+
-
|
38 |
+
name: Set up Docker Buildx
|
39 |
+
uses: docker/setup-buildx-action@v2
|
40 |
+
|
41 |
+
-
|
42 |
+
name: Build and push Docker image
|
43 |
+
uses: docker/build-push-action@v4
|
44 |
+
with:
|
45 |
+
context: .
|
46 |
+
platforms: linux/amd64
|
47 |
+
push: true
|
48 |
+
tags: ${{ steps.meta.outputs.tags }}
|
49 |
+
labels: ${{ steps.meta.outputs.labels }}
|
50 |
+
cache-from: type=gha
|
51 |
+
cache-to: type=gha,mode=max
|
52 |
+
|
.github/workflows/sync.yml
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# .github/workflows/sync.yml
|
2 |
+
name: Sync Fork
|
3 |
+
|
4 |
+
on:
|
5 |
+
schedule:
|
6 |
+
- cron: "0 8 * * *" # 每天0点触发
|
7 |
+
jobs:
|
8 |
+
repo-sync:
|
9 |
+
runs-on: ubuntu-latest
|
10 |
+
steps:
|
11 |
+
- uses: TG908/fork-sync@v1.1
|
12 |
+
with:
|
13 |
+
github_token: ${{ secrets.GITHUB_TOKEN }} # 这个 token action 会默认配置, 这里只需这样写就行
|
14 |
+
owner: Yidadaa # fork 上游项目 owner
|
15 |
+
head: main # fork 上游项目需要同步的分支
|
16 |
+
base: main # 需要同步到本项目的目标分支
|
.gitignore
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
|
8 |
+
# testing
|
9 |
+
/coverage
|
10 |
+
|
11 |
+
# next.js
|
12 |
+
/.next/
|
13 |
+
/out/
|
14 |
+
|
15 |
+
# production
|
16 |
+
/build
|
17 |
+
|
18 |
+
# misc
|
19 |
+
.DS_Store
|
20 |
+
*.pem
|
21 |
+
|
22 |
+
# debug
|
23 |
+
npm-debug.log*
|
24 |
+
yarn-debug.log*
|
25 |
+
yarn-error.log*
|
26 |
+
.pnpm-debug.log*
|
27 |
+
|
28 |
+
# local env files
|
29 |
+
.env*.local
|
30 |
+
|
31 |
+
# vercel
|
32 |
+
.vercel
|
33 |
+
|
34 |
+
# typescript
|
35 |
+
*.tsbuildinfo
|
36 |
+
next-env.d.ts
|
37 |
+
dev
|
38 |
+
|
39 |
+
public/prompts.json
|
.gitpod.yml
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This configuration file was automatically generated by Gitpod.
|
2 |
+
# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
|
3 |
+
# and commit this file to your remote git repository to share the goodness with others.
|
4 |
+
|
5 |
+
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
|
6 |
+
|
7 |
+
tasks:
|
8 |
+
- init: yarn install && yarn run dev
|
9 |
+
command: yarn run dev
|
10 |
+
|
11 |
+
|
.husky/pre-commit
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env sh
|
2 |
+
. "$(dirname -- "$0")/_/husky.sh"
|
3 |
+
|
4 |
+
npx lint-staged
|
.lintstagedrc.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"./app/**/*.{js,ts,jsx,tsx,json,html,css,md}": [
|
3 |
+
"eslint --fix",
|
4 |
+
"prettier --write"
|
5 |
+
]
|
6 |
+
}
|
.prettierrc.js
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
printWidth: 80,
|
3 |
+
tabWidth: 2,
|
4 |
+
useTabs: false,
|
5 |
+
semi: true,
|
6 |
+
singleQuote: false,
|
7 |
+
trailingComma: 'all',
|
8 |
+
bracketSpacing: true,
|
9 |
+
arrowParens: 'always',
|
10 |
+
};
|
.vscode/settings.json
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"typescript.tsdk": "node_modules\\typescript\\lib",
|
3 |
+
"typescript.enablePromptUseWorkspaceTsdk": true
|
4 |
+
}
|
404.html
ADDED
@@ -0,0 +1,1632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html>
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8">
|
5 |
+
<title>CSS 404页面- vfaner.com</title>
|
6 |
+
|
7 |
+
<style>
|
8 |
+
@import url("https://fonts.googleapis.com/css?family=Lato|Russo+One");
|
9 |
+
*,
|
10 |
+
*:after,
|
11 |
+
*:before {
|
12 |
+
box-sizing: border-box;
|
13 |
+
}
|
14 |
+
|
15 |
+
body {
|
16 |
+
padding: 0;
|
17 |
+
margin: 0;
|
18 |
+
}
|
19 |
+
|
20 |
+
.container {
|
21 |
+
position: absolute;
|
22 |
+
top: 0;
|
23 |
+
left: 0;
|
24 |
+
width: 100%;
|
25 |
+
height: 100vh;
|
26 |
+
overflow: hidden;
|
27 |
+
}
|
28 |
+
|
29 |
+
.container-star {
|
30 |
+
background-image: linear-gradient(to bottom, #292256 0%, #8446cf 70%, #a871d6 100%);
|
31 |
+
}
|
32 |
+
.container-star:after {
|
33 |
+
background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 40%, rgba(15, 10, 38, 0.2) 100%);
|
34 |
+
content: "";
|
35 |
+
width: 100%;
|
36 |
+
height: 100%;
|
37 |
+
position: absolute;
|
38 |
+
top: 0;
|
39 |
+
}
|
40 |
+
|
41 |
+
.star-1 {
|
42 |
+
position: absolute;
|
43 |
+
border-radius: 50%;
|
44 |
+
background-color: #ffffff;
|
45 |
+
-webkit-animation: twinkle 5s infinite ease-in-out;
|
46 |
+
animation: twinkle 5s infinite ease-in-out;
|
47 |
+
}
|
48 |
+
.star-1:after {
|
49 |
+
height: 100%;
|
50 |
+
width: 100%;
|
51 |
+
-webkit-transform: rotate(90deg);
|
52 |
+
transform: rotate(90deg);
|
53 |
+
content: "";
|
54 |
+
position: absolute;
|
55 |
+
background-color: #fff;
|
56 |
+
border-radius: 50%;
|
57 |
+
}
|
58 |
+
.star-1:before {
|
59 |
+
background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 60%, rgba(255, 255, 255, 0) 100%);
|
60 |
+
position: absolute;
|
61 |
+
border-radius: 50%;
|
62 |
+
content: "";
|
63 |
+
top: -20%;
|
64 |
+
left: -50%;
|
65 |
+
}
|
66 |
+
|
67 |
+
.star-1:nth-of-type(1) {
|
68 |
+
top: 78vh;
|
69 |
+
left: 44vw;
|
70 |
+
width: 9px;
|
71 |
+
height: 3px;
|
72 |
+
-webkit-animation-delay: 4s;
|
73 |
+
animation-delay: 4s;
|
74 |
+
}
|
75 |
+
.star-1:nth-of-type(1):before {
|
76 |
+
width: 18px;
|
77 |
+
height: 18px;
|
78 |
+
top: -250%;
|
79 |
+
}
|
80 |
+
|
81 |
+
.star-1:nth-of-type(2) {
|
82 |
+
top: 94vh;
|
83 |
+
left: 67vw;
|
84 |
+
width: 6px;
|
85 |
+
height: 2px;
|
86 |
+
-webkit-animation-delay: 3s;
|
87 |
+
animation-delay: 3s;
|
88 |
+
}
|
89 |
+
.star-1:nth-of-type(2):before {
|
90 |
+
width: 12px;
|
91 |
+
height: 12px;
|
92 |
+
top: -250%;
|
93 |
+
}
|
94 |
+
|
95 |
+
.star-1:nth-of-type(3) {
|
96 |
+
top: 41vh;
|
97 |
+
left: 66vw;
|
98 |
+
width: 6px;
|
99 |
+
height: 2px;
|
100 |
+
-webkit-animation-delay: 1s;
|
101 |
+
animation-delay: 1s;
|
102 |
+
}
|
103 |
+
.star-1:nth-of-type(3):before {
|
104 |
+
width: 12px;
|
105 |
+
height: 12px;
|
106 |
+
top: -250%;
|
107 |
+
}
|
108 |
+
|
109 |
+
.star-1:nth-of-type(4) {
|
110 |
+
top: 72vh;
|
111 |
+
left: 77vw;
|
112 |
+
width: 9px;
|
113 |
+
height: 3px;
|
114 |
+
-webkit-animation-delay: 3s;
|
115 |
+
animation-delay: 3s;
|
116 |
+
}
|
117 |
+
.star-1:nth-of-type(4):before {
|
118 |
+
width: 18px;
|
119 |
+
height: 18px;
|
120 |
+
top: -250%;
|
121 |
+
}
|
122 |
+
|
123 |
+
.star-1:nth-of-type(5) {
|
124 |
+
top: 14vh;
|
125 |
+
left: 68vw;
|
126 |
+
width: 9px;
|
127 |
+
height: 3px;
|
128 |
+
-webkit-animation-delay: 4s;
|
129 |
+
animation-delay: 4s;
|
130 |
+
}
|
131 |
+
.star-1:nth-of-type(5):before {
|
132 |
+
width: 18px;
|
133 |
+
height: 18px;
|
134 |
+
top: -250%;
|
135 |
+
}
|
136 |
+
|
137 |
+
.star-1:nth-of-type(6) {
|
138 |
+
top: 26vh;
|
139 |
+
left: 79vw;
|
140 |
+
width: 6px;
|
141 |
+
height: 2px;
|
142 |
+
-webkit-animation-delay: 4s;
|
143 |
+
animation-delay: 4s;
|
144 |
+
}
|
145 |
+
.star-1:nth-of-type(6):before {
|
146 |
+
width: 12px;
|
147 |
+
height: 12px;
|
148 |
+
top: -250%;
|
149 |
+
}
|
150 |
+
|
151 |
+
.star-1:nth-of-type(7) {
|
152 |
+
top: 28vh;
|
153 |
+
left: 84vw;
|
154 |
+
width: 9px;
|
155 |
+
height: 3px;
|
156 |
+
-webkit-animation-delay: 1s;
|
157 |
+
animation-delay: 1s;
|
158 |
+
}
|
159 |
+
.star-1:nth-of-type(7):before {
|
160 |
+
width: 18px;
|
161 |
+
height: 18px;
|
162 |
+
top: -250%;
|
163 |
+
}
|
164 |
+
|
165 |
+
.star-1:nth-of-type(8) {
|
166 |
+
top: 42vh;
|
167 |
+
left: 28vw;
|
168 |
+
width: 4px;
|
169 |
+
height: 1.3333333333px;
|
170 |
+
-webkit-animation-delay: 3s;
|
171 |
+
animation-delay: 3s;
|
172 |
+
}
|
173 |
+
.star-1:nth-of-type(8):before {
|
174 |
+
width: 8px;
|
175 |
+
height: 8px;
|
176 |
+
top: -250%;
|
177 |
+
}
|
178 |
+
|
179 |
+
.star-1:nth-of-type(9) {
|
180 |
+
top: 7vh;
|
181 |
+
left: 99vw;
|
182 |
+
width: 7px;
|
183 |
+
height: 2.3333333333px;
|
184 |
+
-webkit-animation-delay: 1s;
|
185 |
+
animation-delay: 1s;
|
186 |
+
}
|
187 |
+
.star-1:nth-of-type(9):before {
|
188 |
+
width: 14px;
|
189 |
+
height: 14px;
|
190 |
+
top: -250%;
|
191 |
+
}
|
192 |
+
|
193 |
+
.star-1:nth-of-type(10) {
|
194 |
+
top: 97vh;
|
195 |
+
left: 76vw;
|
196 |
+
width: 4px;
|
197 |
+
height: 1.3333333333px;
|
198 |
+
-webkit-animation-delay: 2s;
|
199 |
+
animation-delay: 2s;
|
200 |
+
}
|
201 |
+
.star-1:nth-of-type(10):before {
|
202 |
+
width: 8px;
|
203 |
+
height: 8px;
|
204 |
+
top: -250%;
|
205 |
+
}
|
206 |
+
|
207 |
+
.star-1:nth-of-type(11) {
|
208 |
+
top: 76vh;
|
209 |
+
left: 89vw;
|
210 |
+
width: 9px;
|
211 |
+
height: 3px;
|
212 |
+
-webkit-animation-delay: 4s;
|
213 |
+
animation-delay: 4s;
|
214 |
+
}
|
215 |
+
.star-1:nth-of-type(11):before {
|
216 |
+
width: 18px;
|
217 |
+
height: 18px;
|
218 |
+
top: -250%;
|
219 |
+
}
|
220 |
+
|
221 |
+
.star-1:nth-of-type(12) {
|
222 |
+
top: 16vh;
|
223 |
+
left: 92vw;
|
224 |
+
width: 8px;
|
225 |
+
height: 2.6666666667px;
|
226 |
+
-webkit-animation-delay: 1s;
|
227 |
+
animation-delay: 1s;
|
228 |
+
}
|
229 |
+
.star-1:nth-of-type(12):before {
|
230 |
+
width: 16px;
|
231 |
+
height: 16px;
|
232 |
+
top: -250%;
|
233 |
+
}
|
234 |
+
|
235 |
+
.star-1:nth-of-type(13) {
|
236 |
+
top: 78vh;
|
237 |
+
left: 33vw;
|
238 |
+
width: 6px;
|
239 |
+
height: 2px;
|
240 |
+
-webkit-animation-delay: 4s;
|
241 |
+
animation-delay: 4s;
|
242 |
+
}
|
243 |
+
.star-1:nth-of-type(13):before {
|
244 |
+
width: 12px;
|
245 |
+
height: 12px;
|
246 |
+
top: -250%;
|
247 |
+
}
|
248 |
+
|
249 |
+
.star-1:nth-of-type(14) {
|
250 |
+
top: 12vh;
|
251 |
+
left: 67vw;
|
252 |
+
width: 7px;
|
253 |
+
height: 2.3333333333px;
|
254 |
+
-webkit-animation-delay: 5s;
|
255 |
+
animation-delay: 5s;
|
256 |
+
}
|
257 |
+
.star-1:nth-of-type(14):before {
|
258 |
+
width: 14px;
|
259 |
+
height: 14px;
|
260 |
+
top: -250%;
|
261 |
+
}
|
262 |
+
|
263 |
+
.star-1:nth-of-type(15) {
|
264 |
+
top: 64vh;
|
265 |
+
left: 51vw;
|
266 |
+
width: 4px;
|
267 |
+
height: 1.3333333333px;
|
268 |
+
-webkit-animation-delay: 4s;
|
269 |
+
animation-delay: 4s;
|
270 |
+
}
|
271 |
+
.star-1:nth-of-type(15):before {
|
272 |
+
width: 8px;
|
273 |
+
height: 8px;
|
274 |
+
top: -250%;
|
275 |
+
}
|
276 |
+
|
277 |
+
.star-1:nth-of-type(16) {
|
278 |
+
top: 71vh;
|
279 |
+
left: 95vw;
|
280 |
+
width: 4px;
|
281 |
+
height: 1.3333333333px;
|
282 |
+
-webkit-animation-delay: 4s;
|
283 |
+
animation-delay: 4s;
|
284 |
+
}
|
285 |
+
.star-1:nth-of-type(16):before {
|
286 |
+
width: 8px;
|
287 |
+
height: 8px;
|
288 |
+
top: -250%;
|
289 |
+
}
|
290 |
+
|
291 |
+
.star-1:nth-of-type(17) {
|
292 |
+
top: 56vh;
|
293 |
+
left: 31vw;
|
294 |
+
width: 7px;
|
295 |
+
height: 2.3333333333px;
|
296 |
+
-webkit-animation-delay: 2s;
|
297 |
+
animation-delay: 2s;
|
298 |
+
}
|
299 |
+
.star-1:nth-of-type(17):before {
|
300 |
+
width: 14px;
|
301 |
+
height: 14px;
|
302 |
+
top: -250%;
|
303 |
+
}
|
304 |
+
|
305 |
+
.star-1:nth-of-type(18) {
|
306 |
+
top: 33vh;
|
307 |
+
left: 80vw;
|
308 |
+
width: 6px;
|
309 |
+
height: 2px;
|
310 |
+
-webkit-animation-delay: 5s;
|
311 |
+
animation-delay: 5s;
|
312 |
+
}
|
313 |
+
.star-1:nth-of-type(18):before {
|
314 |
+
width: 12px;
|
315 |
+
height: 12px;
|
316 |
+
top: -250%;
|
317 |
+
}
|
318 |
+
|
319 |
+
.star-1:nth-of-type(19) {
|
320 |
+
top: 30vh;
|
321 |
+
left: 14vw;
|
322 |
+
width: 8px;
|
323 |
+
height: 2.6666666667px;
|
324 |
+
-webkit-animation-delay: 5s;
|
325 |
+
animation-delay: 5s;
|
326 |
+
}
|
327 |
+
.star-1:nth-of-type(19):before {
|
328 |
+
width: 16px;
|
329 |
+
height: 16px;
|
330 |
+
top: -250%;
|
331 |
+
}
|
332 |
+
|
333 |
+
.star-1:nth-of-type(20) {
|
334 |
+
top: 53vh;
|
335 |
+
left: 43vw;
|
336 |
+
width: 6px;
|
337 |
+
height: 2px;
|
338 |
+
-webkit-animation-delay: 3s;
|
339 |
+
animation-delay: 3s;
|
340 |
+
}
|
341 |
+
.star-1:nth-of-type(20):before {
|
342 |
+
width: 12px;
|
343 |
+
height: 12px;
|
344 |
+
top: -250%;
|
345 |
+
}
|
346 |
+
|
347 |
+
.star-1:nth-of-type(21) {
|
348 |
+
top: 32vh;
|
349 |
+
left: 9vw;
|
350 |
+
width: 7px;
|
351 |
+
height: 2.3333333333px;
|
352 |
+
-webkit-animation-delay: 2s;
|
353 |
+
animation-delay: 2s;
|
354 |
+
}
|
355 |
+
.star-1:nth-of-type(21):before {
|
356 |
+
width: 14px;
|
357 |
+
height: 14px;
|
358 |
+
top: -250%;
|
359 |
+
}
|
360 |
+
|
361 |
+
.star-1:nth-of-type(22) {
|
362 |
+
top: 97vh;
|
363 |
+
left: 9vw;
|
364 |
+
width: 5px;
|
365 |
+
height: 1.6666666667px;
|
366 |
+
-webkit-animation-delay: 2s;
|
367 |
+
animation-delay: 2s;
|
368 |
+
}
|
369 |
+
.star-1:nth-of-type(22):before {
|
370 |
+
width: 10px;
|
371 |
+
height: 10px;
|
372 |
+
top: -250%;
|
373 |
+
}
|
374 |
+
|
375 |
+
.star-1:nth-of-type(23) {
|
376 |
+
top: 62vh;
|
377 |
+
left: 6vw;
|
378 |
+
width: 5px;
|
379 |
+
height: 1.6666666667px;
|
380 |
+
-webkit-animation-delay: 4s;
|
381 |
+
animation-delay: 4s;
|
382 |
+
}
|
383 |
+
.star-1:nth-of-type(23):before {
|
384 |
+
width: 10px;
|
385 |
+
height: 10px;
|
386 |
+
top: -250%;
|
387 |
+
}
|
388 |
+
|
389 |
+
.star-1:nth-of-type(24) {
|
390 |
+
top: 57vh;
|
391 |
+
left: 13vw;
|
392 |
+
width: 9px;
|
393 |
+
height: 3px;
|
394 |
+
-webkit-animation-delay: 3s;
|
395 |
+
animation-delay: 3s;
|
396 |
+
}
|
397 |
+
.star-1:nth-of-type(24):before {
|
398 |
+
width: 18px;
|
399 |
+
height: 18px;
|
400 |
+
top: -250%;
|
401 |
+
}
|
402 |
+
|
403 |
+
.star-1:nth-of-type(25) {
|
404 |
+
top: 52vh;
|
405 |
+
left: 60vw;
|
406 |
+
width: 4px;
|
407 |
+
height: 1.3333333333px;
|
408 |
+
-webkit-animation-delay: 5s;
|
409 |
+
animation-delay: 5s;
|
410 |
+
}
|
411 |
+
.star-1:nth-of-type(25):before {
|
412 |
+
width: 8px;
|
413 |
+
height: 8px;
|
414 |
+
top: -250%;
|
415 |
+
}
|
416 |
+
|
417 |
+
.star-1:nth-of-type(26) {
|
418 |
+
top: 5vh;
|
419 |
+
left: 84vw;
|
420 |
+
width: 5px;
|
421 |
+
height: 1.6666666667px;
|
422 |
+
-webkit-animation-delay: 1s;
|
423 |
+
animation-delay: 1s;
|
424 |
+
}
|
425 |
+
.star-1:nth-of-type(26):before {
|
426 |
+
width: 10px;
|
427 |
+
height: 10px;
|
428 |
+
top: -250%;
|
429 |
+
}
|
430 |
+
|
431 |
+
.star-1:nth-of-type(27) {
|
432 |
+
top: 26vh;
|
433 |
+
left: 23vw;
|
434 |
+
width: 8px;
|
435 |
+
height: 2.6666666667px;
|
436 |
+
-webkit-animation-delay: 5s;
|
437 |
+
animation-delay: 5s;
|
438 |
+
}
|
439 |
+
.star-1:nth-of-type(27):before {
|
440 |
+
width: 16px;
|
441 |
+
height: 16px;
|
442 |
+
top: -250%;
|
443 |
+
}
|
444 |
+
|
445 |
+
.star-1:nth-of-type(28) {
|
446 |
+
top: 34vh;
|
447 |
+
left: 3vw;
|
448 |
+
width: 4px;
|
449 |
+
height: 1.3333333333px;
|
450 |
+
-webkit-animation-delay: 4s;
|
451 |
+
animation-delay: 4s;
|
452 |
+
}
|
453 |
+
.star-1:nth-of-type(28):before {
|
454 |
+
width: 8px;
|
455 |
+
height: 8px;
|
456 |
+
top: -250%;
|
457 |
+
}
|
458 |
+
|
459 |
+
.star-1:nth-of-type(29) {
|
460 |
+
top: 40vh;
|
461 |
+
left: 58vw;
|
462 |
+
width: 6px;
|
463 |
+
height: 2px;
|
464 |
+
-webkit-animation-delay: 4s;
|
465 |
+
animation-delay: 4s;
|
466 |
+
}
|
467 |
+
.star-1:nth-of-type(29):before {
|
468 |
+
width: 12px;
|
469 |
+
height: 12px;
|
470 |
+
top: -250%;
|
471 |
+
}
|
472 |
+
|
473 |
+
.star-1:nth-of-type(30) {
|
474 |
+
top: 17vh;
|
475 |
+
left: 55vw;
|
476 |
+
width: 5px;
|
477 |
+
height: 1.6666666667px;
|
478 |
+
-webkit-animation-delay: 5s;
|
479 |
+
animation-delay: 5s;
|
480 |
+
}
|
481 |
+
.star-1:nth-of-type(30):before {
|
482 |
+
width: 10px;
|
483 |
+
height: 10px;
|
484 |
+
top: -250%;
|
485 |
+
}
|
486 |
+
|
487 |
+
.star-2 {
|
488 |
+
position: absolute;
|
489 |
+
border-radius: 50%;
|
490 |
+
background-color: #ffffff;
|
491 |
+
-webkit-animation: twinkle 5s infinite ease-in-out;
|
492 |
+
animation: twinkle 5s infinite ease-in-out;
|
493 |
+
}
|
494 |
+
|
495 |
+
.star-2:nth-of-type(31) {
|
496 |
+
top: 70vh;
|
497 |
+
left: 96vw;
|
498 |
+
width: 2px;
|
499 |
+
height: 2px;
|
500 |
+
-webkit-animation-delay: 2s;
|
501 |
+
animation-delay: 2s;
|
502 |
+
}
|
503 |
+
.star-2:nth-of-type(31):before {
|
504 |
+
width: 4px;
|
505 |
+
height: 4px;
|
506 |
+
top: -250%;
|
507 |
+
}
|
508 |
+
|
509 |
+
.star-2:nth-of-type(32) {
|
510 |
+
top: 88vh;
|
511 |
+
left: 57vw;
|
512 |
+
width: 3px;
|
513 |
+
height: 3px;
|
514 |
+
-webkit-animation-delay: 4s;
|
515 |
+
animation-delay: 4s;
|
516 |
+
}
|
517 |
+
.star-2:nth-of-type(32):before {
|
518 |
+
width: 6px;
|
519 |
+
height: 6px;
|
520 |
+
top: -250%;
|
521 |
+
}
|
522 |
+
|
523 |
+
.star-2:nth-of-type(33) {
|
524 |
+
top: 59vh;
|
525 |
+
left: 48vw;
|
526 |
+
width: 3px;
|
527 |
+
height: 3px;
|
528 |
+
-webkit-animation-delay: 2s;
|
529 |
+
animation-delay: 2s;
|
530 |
+
}
|
531 |
+
.star-2:nth-of-type(33):before {
|
532 |
+
width: 6px;
|
533 |
+
height: 6px;
|
534 |
+
top: -250%;
|
535 |
+
}
|
536 |
+
|
537 |
+
.star-2:nth-of-type(34) {
|
538 |
+
top: 2vh;
|
539 |
+
left: 83vw;
|
540 |
+
width: 2px;
|
541 |
+
height: 2px;
|
542 |
+
-webkit-animation-delay: 2s;
|
543 |
+
animation-delay: 2s;
|
544 |
+
}
|
545 |
+
.star-2:nth-of-type(34):before {
|
546 |
+
width: 4px;
|
547 |
+
height: 4px;
|
548 |
+
top: -250%;
|
549 |
+
}
|
550 |
+
|
551 |
+
.star-2:nth-of-type(35) {
|
552 |
+
top: 8vh;
|
553 |
+
left: 75vw;
|
554 |
+
width: 4px;
|
555 |
+
height: 4px;
|
556 |
+
-webkit-animation-delay: 3s;
|
557 |
+
animation-delay: 3s;
|
558 |
+
}
|
559 |
+
.star-2:nth-of-type(35):before {
|
560 |
+
width: 8px;
|
561 |
+
height: 8px;
|
562 |
+
top: -250%;
|
563 |
+
}
|
564 |
+
|
565 |
+
.star-2:nth-of-type(36) {
|
566 |
+
top: 78vh;
|
567 |
+
left: 8vw;
|
568 |
+
width: 3px;
|
569 |
+
height: 3px;
|
570 |
+
-webkit-animation-delay: 1s;
|
571 |
+
animation-delay: 1s;
|
572 |
+
}
|
573 |
+
.star-2:nth-of-type(36):before {
|
574 |
+
width: 6px;
|
575 |
+
height: 6px;
|
576 |
+
top: -250%;
|
577 |
+
}
|
578 |
+
|
579 |
+
.star-2:nth-of-type(37) {
|
580 |
+
top: 72vh;
|
581 |
+
left: 98vw;
|
582 |
+
width: 2px;
|
583 |
+
height: 2px;
|
584 |
+
-webkit-animation-delay: 2s;
|
585 |
+
animation-delay: 2s;
|
586 |
+
}
|
587 |
+
.star-2:nth-of-type(37):before {
|
588 |
+
width: 4px;
|
589 |
+
height: 4px;
|
590 |
+
top: -250%;
|
591 |
+
}
|
592 |
+
|
593 |
+
.star-2:nth-of-type(38) {
|
594 |
+
top: 34vh;
|
595 |
+
left: 41vw;
|
596 |
+
width: 3px;
|
597 |
+
height: 3px;
|
598 |
+
-webkit-animation-delay: 5s;
|
599 |
+
animation-delay: 5s;
|
600 |
+
}
|
601 |
+
.star-2:nth-of-type(38):before {
|
602 |
+
width: 6px;
|
603 |
+
height: 6px;
|
604 |
+
top: -250%;
|
605 |
+
}
|
606 |
+
|
607 |
+
.star-2:nth-of-type(39) {
|
608 |
+
top: 13vh;
|
609 |
+
left: 5vw;
|
610 |
+
width: 4px;
|
611 |
+
height: 4px;
|
612 |
+
-webkit-animation-delay: 2s;
|
613 |
+
animation-delay: 2s;
|
614 |
+
}
|
615 |
+
.star-2:nth-of-type(39):before {
|
616 |
+
width: 8px;
|
617 |
+
height: 8px;
|
618 |
+
top: -250%;
|
619 |
+
}
|
620 |
+
|
621 |
+
.star-2:nth-of-type(40) {
|
622 |
+
top: 5vh;
|
623 |
+
left: 86vw;
|
624 |
+
width: 2px;
|
625 |
+
height: 2px;
|
626 |
+
-webkit-animation-delay: 4s;
|
627 |
+
animation-delay: 4s;
|
628 |
+
}
|
629 |
+
.star-2:nth-of-type(40):before {
|
630 |
+
width: 4px;
|
631 |
+
height: 4px;
|
632 |
+
top: -250%;
|
633 |
+
}
|
634 |
+
|
635 |
+
.star-2:nth-of-type(41) {
|
636 |
+
top: 7vh;
|
637 |
+
left: 62vw;
|
638 |
+
width: 3px;
|
639 |
+
height: 3px;
|
640 |
+
-webkit-animation-delay: 2s;
|
641 |
+
animation-delay: 2s;
|
642 |
+
}
|
643 |
+
.star-2:nth-of-type(41):before {
|
644 |
+
width: 6px;
|
645 |
+
height: 6px;
|
646 |
+
top: -250%;
|
647 |
+
}
|
648 |
+
|
649 |
+
.star-2:nth-of-type(42) {
|
650 |
+
top: 36vh;
|
651 |
+
left: 44vw;
|
652 |
+
width: 2px;
|
653 |
+
height: 2px;
|
654 |
+
-webkit-animation-delay: 2s;
|
655 |
+
animation-delay: 2s;
|
656 |
+
}
|
657 |
+
.star-2:nth-of-type(42):before {
|
658 |
+
width: 4px;
|
659 |
+
height: 4px;
|
660 |
+
top: -250%;
|
661 |
+
}
|
662 |
+
|
663 |
+
.star-2:nth-of-type(43) {
|
664 |
+
top: 74vh;
|
665 |
+
left: 47vw;
|
666 |
+
width: 3px;
|
667 |
+
height: 3px;
|
668 |
+
-webkit-animation-delay: 1s;
|
669 |
+
animation-delay: 1s;
|
670 |
+
}
|
671 |
+
.star-2:nth-of-type(43):before {
|
672 |
+
width: 6px;
|
673 |
+
height: 6px;
|
674 |
+
top: -250%;
|
675 |
+
}
|
676 |
+
|
677 |
+
.star-2:nth-of-type(44) {
|
678 |
+
top: 72vh;
|
679 |
+
left: 86vw;
|
680 |
+
width: 2px;
|
681 |
+
height: 2px;
|
682 |
+
-webkit-animation-delay: 4s;
|
683 |
+
animation-delay: 4s;
|
684 |
+
}
|
685 |
+
.star-2:nth-of-type(44):before {
|
686 |
+
width: 4px;
|
687 |
+
height: 4px;
|
688 |
+
top: -250%;
|
689 |
+
}
|
690 |
+
|
691 |
+
.star-2:nth-of-type(45) {
|
692 |
+
top: 26vh;
|
693 |
+
left: 40vw;
|
694 |
+
width: 4px;
|
695 |
+
height: 4px;
|
696 |
+
-webkit-animation-delay: 2s;
|
697 |
+
animation-delay: 2s;
|
698 |
+
}
|
699 |
+
.star-2:nth-of-type(45):before {
|
700 |
+
width: 8px;
|
701 |
+
height: 8px;
|
702 |
+
top: -250%;
|
703 |
+
}
|
704 |
+
|
705 |
+
.star-2:nth-of-type(46) {
|
706 |
+
top: 41vh;
|
707 |
+
left: 39vw;
|
708 |
+
width: 3px;
|
709 |
+
height: 3px;
|
710 |
+
-webkit-animation-delay: 2s;
|
711 |
+
animation-delay: 2s;
|
712 |
+
}
|
713 |
+
.star-2:nth-of-type(46):before {
|
714 |
+
width: 6px;
|
715 |
+
height: 6px;
|
716 |
+
top: -250%;
|
717 |
+
}
|
718 |
+
|
719 |
+
.star-2:nth-of-type(47) {
|
720 |
+
top: 16vh;
|
721 |
+
left: 36vw;
|
722 |
+
width: 4px;
|
723 |
+
height: 4px;
|
724 |
+
-webkit-animation-delay: 4s;
|
725 |
+
animation-delay: 4s;
|
726 |
+
}
|
727 |
+
.star-2:nth-of-type(47):before {
|
728 |
+
width: 8px;
|
729 |
+
height: 8px;
|
730 |
+
top: -250%;
|
731 |
+
}
|
732 |
+
|
733 |
+
.star-2:nth-of-type(48) {
|
734 |
+
top: 96vh;
|
735 |
+
left: 37vw;
|
736 |
+
width: 4px;
|
737 |
+
height: 4px;
|
738 |
+
-webkit-animation-delay: 3s;
|
739 |
+
animation-delay: 3s;
|
740 |
+
}
|
741 |
+
.star-2:nth-of-type(48):before {
|
742 |
+
width: 8px;
|
743 |
+
height: 8px;
|
744 |
+
top: -250%;
|
745 |
+
}
|
746 |
+
|
747 |
+
.star-2:nth-of-type(49) {
|
748 |
+
top: 18vh;
|
749 |
+
left: 8vw;
|
750 |
+
width: 4px;
|
751 |
+
height: 4px;
|
752 |
+
-webkit-animation-delay: 1s;
|
753 |
+
animation-delay: 1s;
|
754 |
+
}
|
755 |
+
.star-2:nth-of-type(49):before {
|
756 |
+
width: 8px;
|
757 |
+
height: 8px;
|
758 |
+
top: -250%;
|
759 |
+
}
|
760 |
+
|
761 |
+
.star-2:nth-of-type(50) {
|
762 |
+
top: 56vh;
|
763 |
+
left: 31vw;
|
764 |
+
width: 4px;
|
765 |
+
height: 4px;
|
766 |
+
-webkit-animation-delay: 4s;
|
767 |
+
animation-delay: 4s;
|
768 |
+
}
|
769 |
+
.star-2:nth-of-type(50):before {
|
770 |
+
width: 8px;
|
771 |
+
height: 8px;
|
772 |
+
top: -250%;
|
773 |
+
}
|
774 |
+
|
775 |
+
.star-2:nth-of-type(51) {
|
776 |
+
top: 24vh;
|
777 |
+
left: 69vw;
|
778 |
+
width: 3px;
|
779 |
+
height: 3px;
|
780 |
+
-webkit-animation-delay: 3s;
|
781 |
+
animation-delay: 3s;
|
782 |
+
}
|
783 |
+
.star-2:nth-of-type(51):before {
|
784 |
+
width: 6px;
|
785 |
+
height: 6px;
|
786 |
+
top: -250%;
|
787 |
+
}
|
788 |
+
|
789 |
+
.star-2:nth-of-type(52) {
|
790 |
+
top: 52vh;
|
791 |
+
left: 17vw;
|
792 |
+
width: 3px;
|
793 |
+
height: 3px;
|
794 |
+
-webkit-animation-delay: 3s;
|
795 |
+
animation-delay: 3s;
|
796 |
+
}
|
797 |
+
.star-2:nth-of-type(52):before {
|
798 |
+
width: 6px;
|
799 |
+
height: 6px;
|
800 |
+
top: -250%;
|
801 |
+
}
|
802 |
+
|
803 |
+
.star-2:nth-of-type(53) {
|
804 |
+
top: 35vh;
|
805 |
+
left: 59vw;
|
806 |
+
width: 2px;
|
807 |
+
height: 2px;
|
808 |
+
-webkit-animation-delay: 3s;
|
809 |
+
animation-delay: 3s;
|
810 |
+
}
|
811 |
+
.star-2:nth-of-type(53):before {
|
812 |
+
width: 4px;
|
813 |
+
height: 4px;
|
814 |
+
top: -250%;
|
815 |
+
}
|
816 |
+
|
817 |
+
.star-2:nth-of-type(54) {
|
818 |
+
top: 46vh;
|
819 |
+
left: 73vw;
|
820 |
+
width: 4px;
|
821 |
+
height: 4px;
|
822 |
+
-webkit-animation-delay: 2s;
|
823 |
+
animation-delay: 2s;
|
824 |
+
}
|
825 |
+
.star-2:nth-of-type(54):before {
|
826 |
+
width: 8px;
|
827 |
+
height: 8px;
|
828 |
+
top: -250%;
|
829 |
+
}
|
830 |
+
|
831 |
+
.star-2:nth-of-type(55) {
|
832 |
+
top: 38vh;
|
833 |
+
left: 35vw;
|
834 |
+
width: 4px;
|
835 |
+
height: 4px;
|
836 |
+
-webkit-animation-delay: 1s;
|
837 |
+
animation-delay: 1s;
|
838 |
+
}
|
839 |
+
.star-2:nth-of-type(55):before {
|
840 |
+
width: 8px;
|
841 |
+
height: 8px;
|
842 |
+
top: -250%;
|
843 |
+
}
|
844 |
+
|
845 |
+
.star-2:nth-of-type(56) {
|
846 |
+
top: 34vh;
|
847 |
+
left: 66vw;
|
848 |
+
width: 3px;
|
849 |
+
height: 3px;
|
850 |
+
-webkit-animation-delay: 2s;
|
851 |
+
animation-delay: 2s;
|
852 |
+
}
|
853 |
+
.star-2:nth-of-type(56):before {
|
854 |
+
width: 6px;
|
855 |
+
height: 6px;
|
856 |
+
top: -250%;
|
857 |
+
}
|
858 |
+
|
859 |
+
.star-2:nth-of-type(57) {
|
860 |
+
top: 80vh;
|
861 |
+
left: 76vw;
|
862 |
+
width: 3px;
|
863 |
+
height: 3px;
|
864 |
+
-webkit-animation-delay: 5s;
|
865 |
+
animation-delay: 5s;
|
866 |
+
}
|
867 |
+
.star-2:nth-of-type(57):before {
|
868 |
+
width: 6px;
|
869 |
+
height: 6px;
|
870 |
+
top: -250%;
|
871 |
+
}
|
872 |
+
|
873 |
+
.star-2:nth-of-type(58) {
|
874 |
+
top: 45vh;
|
875 |
+
left: 49vw;
|
876 |
+
width: 2px;
|
877 |
+
height: 2px;
|
878 |
+
-webkit-animation-delay: 3s;
|
879 |
+
animation-delay: 3s;
|
880 |
+
}
|
881 |
+
.star-2:nth-of-type(58):before {
|
882 |
+
width: 4px;
|
883 |
+
height: 4px;
|
884 |
+
top: -250%;
|
885 |
+
}
|
886 |
+
|
887 |
+
.star-2:nth-of-type(59) {
|
888 |
+
top: 8vh;
|
889 |
+
left: 4vw;
|
890 |
+
width: 4px;
|
891 |
+
height: 4px;
|
892 |
+
-webkit-animation-delay: 1s;
|
893 |
+
animation-delay: 1s;
|
894 |
+
}
|
895 |
+
.star-2:nth-of-type(59):before {
|
896 |
+
width: 8px;
|
897 |
+
height: 8px;
|
898 |
+
top: -250%;
|
899 |
+
}
|
900 |
+
|
901 |
+
.star-2:nth-of-type(60) {
|
902 |
+
top: 71vh;
|
903 |
+
left: 93vw;
|
904 |
+
width: 2px;
|
905 |
+
height: 2px;
|
906 |
+
-webkit-animation-delay: 3s;
|
907 |
+
animation-delay: 3s;
|
908 |
+
}
|
909 |
+
.star-2:nth-of-type(60):before {
|
910 |
+
width: 4px;
|
911 |
+
height: 4px;
|
912 |
+
top: -250%;
|
913 |
+
}
|
914 |
+
|
915 |
+
.container-title {
|
916 |
+
width: 600px;
|
917 |
+
height: 450px;
|
918 |
+
left: 50%;
|
919 |
+
top: 50%;
|
920 |
+
-webkit-transform: translate(-50%, -50%);
|
921 |
+
transform: translate(-50%, -50%);
|
922 |
+
position: absolute;
|
923 |
+
color: white;
|
924 |
+
line-height: 1;
|
925 |
+
font-weight: 700;
|
926 |
+
text-align: center;
|
927 |
+
justify-content: center;
|
928 |
+
align-items: center;
|
929 |
+
flex-direction: column;
|
930 |
+
display: flex;
|
931 |
+
}
|
932 |
+
|
933 |
+
.title > * {
|
934 |
+
display: inline-block;
|
935 |
+
font-size: 200px;
|
936 |
+
}
|
937 |
+
|
938 |
+
.number {
|
939 |
+
text-shadow: 20px 20px 20px rgba(0, 0, 0, 0.2);
|
940 |
+
padding: 0 0.2em;
|
941 |
+
font-family: 'Russo One', sans-serif;
|
942 |
+
}
|
943 |
+
|
944 |
+
.subtitle {
|
945 |
+
font-size: 25px;
|
946 |
+
margin-top: 1.5em;
|
947 |
+
font-family: "Lato", sans-serif;
|
948 |
+
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
949 |
+
}
|
950 |
+
|
951 |
+
button {
|
952 |
+
font-size: 22px;
|
953 |
+
margin-top: 1.5em;
|
954 |
+
padding: 0.5em 1em;
|
955 |
+
letter-spacing: 1px;
|
956 |
+
font-family: "Lato", sans-serif;
|
957 |
+
color: white;
|
958 |
+
background-color: transparent;
|
959 |
+
border: 0;
|
960 |
+
cursor: pointer;
|
961 |
+
z-index: 999;
|
962 |
+
border: 2px solid white;
|
963 |
+
border-radius: 5px;
|
964 |
+
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
965 |
+
transition: opacity 0.2s ease;
|
966 |
+
}
|
967 |
+
button:hover {
|
968 |
+
opacity: 0.7;
|
969 |
+
}
|
970 |
+
button:focus {
|
971 |
+
outline: 0;
|
972 |
+
}
|
973 |
+
|
974 |
+
.moon {
|
975 |
+
position: relative;
|
976 |
+
border-radius: 50%;
|
977 |
+
width: 160px;
|
978 |
+
height: 160px;
|
979 |
+
z-index: 2;
|
980 |
+
background-color: #fff;
|
981 |
+
box-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #fff, 0 0 70px #fff, 0 0 80px #fff, 0 0 100px #ff1177;
|
982 |
+
-webkit-animation: rotate 5s ease-in-out infinite;
|
983 |
+
animation: rotate 5s ease-in-out infinite;
|
984 |
+
}
|
985 |
+
.moon .face {
|
986 |
+
top: 60%;
|
987 |
+
left: 47%;
|
988 |
+
position: absolute;
|
989 |
+
}
|
990 |
+
.moon .face .mouth {
|
991 |
+
border-top-left-radius: 50%;
|
992 |
+
border-bottom-right-radius: 50%;
|
993 |
+
border-top-right-radius: 50%;
|
994 |
+
background-color: #5c3191;
|
995 |
+
width: 25px;
|
996 |
+
height: 25px;
|
997 |
+
position: absolute;
|
998 |
+
-webkit-animation: snore 5s ease-in-out infinite;
|
999 |
+
animation: snore 5s ease-in-out infinite;
|
1000 |
+
-webkit-transform: rotate(45deg);
|
1001 |
+
transform: rotate(45deg);
|
1002 |
+
box-shadow: inset -4px -4px 4px rgba(0, 0, 0, 0.3);
|
1003 |
+
}
|
1004 |
+
.moon .face .eyes {
|
1005 |
+
position: absolute;
|
1006 |
+
top: -30px;
|
1007 |
+
left: -30px;
|
1008 |
+
}
|
1009 |
+
.moon .face .eyes .eye-left,
|
1010 |
+
.moon .face .eyes .eye-right {
|
1011 |
+
border: 4px solid #5c3191;
|
1012 |
+
width: 30px;
|
1013 |
+
height: 15px;
|
1014 |
+
border-bottom-left-radius: 100px;
|
1015 |
+
border-bottom-right-radius: 100px;
|
1016 |
+
border-top: 0;
|
1017 |
+
position: absolute;
|
1018 |
+
}
|
1019 |
+
.moon .face .eyes .eye-left:before, .moon .face .eyes .eye-left:after,
|
1020 |
+
.moon .face .eyes .eye-right:before,
|
1021 |
+
.moon .face .eyes .eye-right:after {
|
1022 |
+
content: "";
|
1023 |
+
position: absolute;
|
1024 |
+
border-radius: 50%;
|
1025 |
+
width: 4px;
|
1026 |
+
height: 4px;
|
1027 |
+
background-color: #5c3191;
|
1028 |
+
top: -2px;
|
1029 |
+
left: -4px;
|
1030 |
+
}
|
1031 |
+
.moon .face .eyes .eye-left:after,
|
1032 |
+
.moon .face .eyes .eye-right:after {
|
1033 |
+
left: auto;
|
1034 |
+
right: -4px;
|
1035 |
+
}
|
1036 |
+
.moon .face .eyes .eye-right {
|
1037 |
+
left: 50px;
|
1038 |
+
}
|
1039 |
+
|
1040 |
+
.container-bird {
|
1041 |
+
-webkit-perspective: 2000px;
|
1042 |
+
perspective: 2000px;
|
1043 |
+
width: 100%;
|
1044 |
+
height: 100%;
|
1045 |
+
position: absolute;
|
1046 |
+
top: 0;
|
1047 |
+
left: 0;
|
1048 |
+
bottom: 0;
|
1049 |
+
right: 0;
|
1050 |
+
}
|
1051 |
+
|
1052 |
+
.bird {
|
1053 |
+
position: absolute;
|
1054 |
+
z-index: 1000;
|
1055 |
+
left: 50%;
|
1056 |
+
top: 50%;
|
1057 |
+
height: 40px;
|
1058 |
+
width: 50px;
|
1059 |
+
-webkit-transform: translate3d(-100vw, 0, 0) rotateY(90deg);
|
1060 |
+
transform: translate3d(-100vw, 0, 0) rotateY(90deg);
|
1061 |
+
-webkit-transform-style: preserve-3d;
|
1062 |
+
transform-style: preserve-3d;
|
1063 |
+
}
|
1064 |
+
|
1065 |
+
.bird-container {
|
1066 |
+
left: 0;
|
1067 |
+
top: 0;
|
1068 |
+
width: 100%;
|
1069 |
+
height: 100%;
|
1070 |
+
-webkit-transform-style: preserve-3d;
|
1071 |
+
transform-style: preserve-3d;
|
1072 |
+
-webkit-transform: translate3d(50px, 30px, -300px);
|
1073 |
+
transform: translate3d(50px, 30px, -300px);
|
1074 |
+
}
|
1075 |
+
|
1076 |
+
.wing {
|
1077 |
+
position: absolute;
|
1078 |
+
left: 0;
|
1079 |
+
top: 0;
|
1080 |
+
right: 0;
|
1081 |
+
bottom: 0;
|
1082 |
+
border-radius: 3px;
|
1083 |
+
-webkit-transform-style: preserve-3d;
|
1084 |
+
transform-style: preserve-3d;
|
1085 |
+
-webkit-transform-origin: center bottom;
|
1086 |
+
transform-origin: center bottom;
|
1087 |
+
z-index: 300;
|
1088 |
+
}
|
1089 |
+
|
1090 |
+
.wing-left {
|
1091 |
+
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
1092 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-30deg);
|
1093 |
+
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
1094 |
+
-webkit-animation: wingLeft 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1095 |
+
animation: wingLeft 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1096 |
+
}
|
1097 |
+
|
1098 |
+
.wing-right {
|
1099 |
+
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
1100 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-30deg);
|
1101 |
+
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
1102 |
+
-webkit-animation: wingRight 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1103 |
+
animation: wingRight 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1104 |
+
}
|
1105 |
+
|
1106 |
+
.wing-right-top,
|
1107 |
+
.wing-left-top {
|
1108 |
+
border-right: 25px solid transparent;
|
1109 |
+
border-left: 25px solid transparent;
|
1110 |
+
top: -20px;
|
1111 |
+
width: 100%;
|
1112 |
+
position: absolute;
|
1113 |
+
-webkit-transform-origin: 100% 100%;
|
1114 |
+
transform-origin: 100% 100%;
|
1115 |
+
}
|
1116 |
+
|
1117 |
+
.wing-right-top {
|
1118 |
+
border-bottom: 20px solid #b8a5d1;
|
1119 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(60deg);
|
1120 |
+
transform: translate3d(0, 0, 0) rotateX(60deg);
|
1121 |
+
-webkit-animation: wingRightTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1122 |
+
animation: wingRightTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
.wing-left-top {
|
1126 |
+
border-bottom: 20px solid #7979a8;
|
1127 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-60deg);
|
1128 |
+
transform: translate3d(0, 0, 0) rotateX(-60deg);
|
1129 |
+
-webkit-animation: wingLeftTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1130 |
+
animation: wingLeftTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
.bird-anim:nth-child(1) {
|
1134 |
+
-webkit-animation: bird1 30s linear infinite forwards;
|
1135 |
+
animation: bird1 30s linear infinite forwards;
|
1136 |
+
}
|
1137 |
+
|
1138 |
+
.bird-anim:nth-child(2) {
|
1139 |
+
-webkit-animation: bird2 30s linear infinite forwards;
|
1140 |
+
animation: bird2 30s linear infinite forwards;
|
1141 |
+
-webkit-animation-delay: 3s;
|
1142 |
+
animation-delay: 3s;
|
1143 |
+
z-index: -1;
|
1144 |
+
}
|
1145 |
+
|
1146 |
+
.bird-anim:nth-child(3) {
|
1147 |
+
-webkit-animation: bird3 30s linear infinite forwards;
|
1148 |
+
animation: bird3 30s linear infinite forwards;
|
1149 |
+
-webkit-animation-delay: 5s;
|
1150 |
+
animation-delay: 5s;
|
1151 |
+
}
|
1152 |
+
|
1153 |
+
.bird-anim:nth-child(4) {
|
1154 |
+
-webkit-animation: bird4 30s linear infinite forwards;
|
1155 |
+
animation: bird4 30s linear infinite forwards;
|
1156 |
+
-webkit-animation-delay: 7s;
|
1157 |
+
animation-delay: 7s;
|
1158 |
+
}
|
1159 |
+
|
1160 |
+
.bird-anim:nth-child(5) {
|
1161 |
+
-webkit-animation: bird5 30s linear infinite forwards;
|
1162 |
+
animation: bird5 30s linear infinite forwards;
|
1163 |
+
-webkit-animation-delay: 14s;
|
1164 |
+
animation-delay: 14s;
|
1165 |
+
}
|
1166 |
+
|
1167 |
+
.bird-anim:nth-child(6) {
|
1168 |
+
-webkit-animation: bird6 30s linear infinite forwards;
|
1169 |
+
animation: bird6 30s linear infinite forwards;
|
1170 |
+
-webkit-animation-delay: 10s;
|
1171 |
+
animation-delay: 10s;
|
1172 |
+
z-index: -1;
|
1173 |
+
}
|
1174 |
+
|
1175 |
+
@-webkit-keyframes rotate {
|
1176 |
+
0%, 100% {
|
1177 |
+
-webkit-transform: rotate(-8deg);
|
1178 |
+
transform: rotate(-8deg);
|
1179 |
+
}
|
1180 |
+
50% {
|
1181 |
+
-webkit-transform: rotate(0deg);
|
1182 |
+
transform: rotate(0deg);
|
1183 |
+
}
|
1184 |
+
}
|
1185 |
+
|
1186 |
+
@keyframes rotate {
|
1187 |
+
0%, 100% {
|
1188 |
+
-webkit-transform: rotate(-8deg);
|
1189 |
+
transform: rotate(-8deg);
|
1190 |
+
}
|
1191 |
+
50% {
|
1192 |
+
-webkit-transform: rotate(0deg);
|
1193 |
+
transform: rotate(0deg);
|
1194 |
+
}
|
1195 |
+
}
|
1196 |
+
@-webkit-keyframes snore {
|
1197 |
+
0%, 100% {
|
1198 |
+
-webkit-transform: scale(1) rotate(30deg);
|
1199 |
+
transform: scale(1) rotate(30deg);
|
1200 |
+
}
|
1201 |
+
50% {
|
1202 |
+
-webkit-transform: scale(0.5) rotate(30deg);
|
1203 |
+
transform: scale(0.5) rotate(30deg);
|
1204 |
+
border-bottom-left-radius: 50%;
|
1205 |
+
}
|
1206 |
+
}
|
1207 |
+
@keyframes snore {
|
1208 |
+
0%, 100% {
|
1209 |
+
-webkit-transform: scale(1) rotate(30deg);
|
1210 |
+
transform: scale(1) rotate(30deg);
|
1211 |
+
}
|
1212 |
+
50% {
|
1213 |
+
-webkit-transform: scale(0.5) rotate(30deg);
|
1214 |
+
transform: scale(0.5) rotate(30deg);
|
1215 |
+
border-bottom-left-radius: 50%;
|
1216 |
+
}
|
1217 |
+
}
|
1218 |
+
@-webkit-keyframes twinkle {
|
1219 |
+
0%, 100% {
|
1220 |
+
opacity: 0.7;
|
1221 |
+
}
|
1222 |
+
50% {
|
1223 |
+
opacity: 0.3;
|
1224 |
+
}
|
1225 |
+
}
|
1226 |
+
@keyframes twinkle {
|
1227 |
+
0%, 100% {
|
1228 |
+
opacity: 0.7;
|
1229 |
+
}
|
1230 |
+
50% {
|
1231 |
+
opacity: 0.3;
|
1232 |
+
}
|
1233 |
+
}
|
1234 |
+
@-webkit-keyframes wingLeft {
|
1235 |
+
0%, 100% {
|
1236 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-50deg);
|
1237 |
+
transform: translate3d(0, 0, 0) rotateX(-50deg);
|
1238 |
+
}
|
1239 |
+
50% {
|
1240 |
+
-webkit-transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
1241 |
+
transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
1242 |
+
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
1243 |
+
}
|
1244 |
+
}
|
1245 |
+
@keyframes wingLeft {
|
1246 |
+
0%, 100% {
|
1247 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-50deg);
|
1248 |
+
transform: translate3d(0, 0, 0) rotateX(-50deg);
|
1249 |
+
}
|
1250 |
+
50% {
|
1251 |
+
-webkit-transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
1252 |
+
transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
1253 |
+
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
1254 |
+
}
|
1255 |
+
}
|
1256 |
+
@-webkit-keyframes wingLeftTop {
|
1257 |
+
0%, 100% {
|
1258 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-10deg);
|
1259 |
+
transform: translate3d(0, 0, 0) rotateX(-10deg);
|
1260 |
+
}
|
1261 |
+
50% {
|
1262 |
+
-webkit-transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
1263 |
+
transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
1264 |
+
border-bottom: 20px solid #b8a5d1;
|
1265 |
+
}
|
1266 |
+
}
|
1267 |
+
@keyframes wingLeftTop {
|
1268 |
+
0%, 100% {
|
1269 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(-10deg);
|
1270 |
+
transform: translate3d(0, 0, 0) rotateX(-10deg);
|
1271 |
+
}
|
1272 |
+
50% {
|
1273 |
+
-webkit-transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
1274 |
+
transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
1275 |
+
border-bottom: 20px solid #b8a5d1;
|
1276 |
+
}
|
1277 |
+
}
|
1278 |
+
@-webkit-keyframes wingRight {
|
1279 |
+
0%, 100% {
|
1280 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(50deg);
|
1281 |
+
transform: translate3d(0, 0, 0) rotateX(50deg);
|
1282 |
+
}
|
1283 |
+
50% {
|
1284 |
+
-webkit-transform: translate3d(0, -20px, 0) rotateX(130deg);
|
1285 |
+
transform: translate3d(0, -20px, 0) rotateX(130deg);
|
1286 |
+
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
1287 |
+
}
|
1288 |
+
}
|
1289 |
+
@keyframes wingRight {
|
1290 |
+
0%, 100% {
|
1291 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(50deg);
|
1292 |
+
transform: translate3d(0, 0, 0) rotateX(50deg);
|
1293 |
+
}
|
1294 |
+
50% {
|
1295 |
+
-webkit-transform: translate3d(0, -20px, 0) rotateX(130deg);
|
1296 |
+
transform: translate3d(0, -20px, 0) rotateX(130deg);
|
1297 |
+
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
1298 |
+
}
|
1299 |
+
}
|
1300 |
+
@-webkit-keyframes wingRightTop {
|
1301 |
+
0%, 100% {
|
1302 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(10deg);
|
1303 |
+
transform: translate3d(0, 0, 0) rotateX(10deg);
|
1304 |
+
}
|
1305 |
+
50% {
|
1306 |
+
-webkit-transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
1307 |
+
transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
1308 |
+
border-bottom: 20px solid #7979a8;
|
1309 |
+
}
|
1310 |
+
}
|
1311 |
+
@keyframes wingRightTop {
|
1312 |
+
0%, 100% {
|
1313 |
+
-webkit-transform: translate3d(0, 0, 0) rotateX(10deg);
|
1314 |
+
transform: translate3d(0, 0, 0) rotateX(10deg);
|
1315 |
+
}
|
1316 |
+
50% {
|
1317 |
+
-webkit-transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
1318 |
+
transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
1319 |
+
border-bottom: 20px solid #7979a8;
|
1320 |
+
}
|
1321 |
+
}
|
1322 |
+
@-webkit-keyframes bird1 {
|
1323 |
+
0% {
|
1324 |
+
-webkit-transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
1325 |
+
transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
1326 |
+
}
|
1327 |
+
100% {
|
1328 |
+
-webkit-transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
1329 |
+
transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
1330 |
+
}
|
1331 |
+
}
|
1332 |
+
@keyframes bird1 {
|
1333 |
+
0% {
|
1334 |
+
-webkit-transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
1335 |
+
transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
1336 |
+
}
|
1337 |
+
100% {
|
1338 |
+
-webkit-transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
1339 |
+
transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
1340 |
+
}
|
1341 |
+
}
|
1342 |
+
@-webkit-keyframes bird2 {
|
1343 |
+
0%,
|
1344 |
+
15% {
|
1345 |
+
-webkit-transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
1346 |
+
transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
1347 |
+
}
|
1348 |
+
100% {
|
1349 |
+
-webkit-transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
1350 |
+
transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
1351 |
+
}
|
1352 |
+
}
|
1353 |
+
@keyframes bird2 {
|
1354 |
+
0%,
|
1355 |
+
15% {
|
1356 |
+
-webkit-transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
1357 |
+
transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
1358 |
+
}
|
1359 |
+
100% {
|
1360 |
+
-webkit-transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
1361 |
+
transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
1362 |
+
}
|
1363 |
+
}
|
1364 |
+
@-webkit-keyframes bird3 {
|
1365 |
+
0% {
|
1366 |
+
-webkit-transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1367 |
+
transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1368 |
+
}
|
1369 |
+
100% {
|
1370 |
+
-webkit-transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1371 |
+
transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1372 |
+
}
|
1373 |
+
}
|
1374 |
+
@keyframes bird3 {
|
1375 |
+
0% {
|
1376 |
+
-webkit-transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1377 |
+
transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1378 |
+
}
|
1379 |
+
100% {
|
1380 |
+
-webkit-transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1381 |
+
transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
1382 |
+
}
|
1383 |
+
}
|
1384 |
+
@-webkit-keyframes bird4 {
|
1385 |
+
0% {
|
1386 |
+
-webkit-transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1387 |
+
transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1388 |
+
}
|
1389 |
+
100% {
|
1390 |
+
-webkit-transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1391 |
+
transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1392 |
+
}
|
1393 |
+
}
|
1394 |
+
@keyframes bird4 {
|
1395 |
+
0% {
|
1396 |
+
-webkit-transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1397 |
+
transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1398 |
+
}
|
1399 |
+
100% {
|
1400 |
+
-webkit-transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1401 |
+
transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
1402 |
+
}
|
1403 |
+
}
|
1404 |
+
@-webkit-keyframes bird5 {
|
1405 |
+
0%,
|
1406 |
+
5% {
|
1407 |
+
-webkit-transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1408 |
+
transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1409 |
+
}
|
1410 |
+
100% {
|
1411 |
+
-webkit-transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1412 |
+
transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1413 |
+
}
|
1414 |
+
}
|
1415 |
+
@keyframes bird5 {
|
1416 |
+
0%,
|
1417 |
+
5% {
|
1418 |
+
-webkit-transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1419 |
+
transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1420 |
+
}
|
1421 |
+
100% {
|
1422 |
+
-webkit-transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1423 |
+
transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
1424 |
+
}
|
1425 |
+
}
|
1426 |
+
@-webkit-keyframes bird6 {
|
1427 |
+
0%, 10% {
|
1428 |
+
-webkit-transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
1429 |
+
transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
1430 |
+
}
|
1431 |
+
100% {
|
1432 |
+
-webkit-transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
1433 |
+
transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
1434 |
+
}
|
1435 |
+
}
|
1436 |
+
@keyframes bird6 {
|
1437 |
+
0%, 10% {
|
1438 |
+
-webkit-transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
1439 |
+
transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
1440 |
+
}
|
1441 |
+
100% {
|
1442 |
+
-webkit-transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
1443 |
+
transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
1444 |
+
}
|
1445 |
+
}
|
1446 |
+
@media screen and (max-width: 580px) {
|
1447 |
+
.container-404 {
|
1448 |
+
width: 100%;
|
1449 |
+
}
|
1450 |
+
|
1451 |
+
.number {
|
1452 |
+
font-size: 100px;
|
1453 |
+
}
|
1454 |
+
|
1455 |
+
.subtitle {
|
1456 |
+
font-size: 20px;
|
1457 |
+
padding: 0 1em;
|
1458 |
+
}
|
1459 |
+
|
1460 |
+
.moon {
|
1461 |
+
width: 100px;
|
1462 |
+
height: 100px;
|
1463 |
+
}
|
1464 |
+
|
1465 |
+
.face {
|
1466 |
+
-webkit-transform: scale(0.7);
|
1467 |
+
transform: scale(0.7);
|
1468 |
+
}
|
1469 |
+
}
|
1470 |
+
</style>
|
1471 |
+
|
1472 |
+
</head>
|
1473 |
+
<body>
|
1474 |
+
|
1475 |
+
<div class="container container-star">
|
1476 |
+
<div class="star-1"></div>
|
1477 |
+
<div class="star-1"></div>
|
1478 |
+
<div class="star-1"></div>
|
1479 |
+
<div class="star-1"></div>
|
1480 |
+
<div class="star-1"></div>
|
1481 |
+
<div class="star-1"></div>
|
1482 |
+
<div class="star-1"></div>
|
1483 |
+
<div class="star-1"></div>
|
1484 |
+
<div class="star-1"></div>
|
1485 |
+
<div class="star-1"></div>
|
1486 |
+
<div class="star-1"></div>
|
1487 |
+
<div class="star-1"></div>
|
1488 |
+
<div class="star-1"></div>
|
1489 |
+
<div class="star-1"></div>
|
1490 |
+
<div class="star-1"></div>
|
1491 |
+
<div class="star-1"></div>
|
1492 |
+
<div class="star-1"></div>
|
1493 |
+
<div class="star-1"></div>
|
1494 |
+
<div class="star-1"></div>
|
1495 |
+
<div class="star-1"></div>
|
1496 |
+
<div class="star-1"></div>
|
1497 |
+
<div class="star-1"></div>
|
1498 |
+
<div class="star-1"></div>
|
1499 |
+
<div class="star-1"></div>
|
1500 |
+
<div class="star-1"></div>
|
1501 |
+
<div class="star-1"></div>
|
1502 |
+
<div class="star-1"></div>
|
1503 |
+
<div class="star-1"></div>
|
1504 |
+
<div class="star-1"></div>
|
1505 |
+
<div class="star-1"></div>
|
1506 |
+
<div class="star-2"></div>
|
1507 |
+
<div class="star-2"></div>
|
1508 |
+
<div class="star-2"></div>
|
1509 |
+
<div class="star-2"></div>
|
1510 |
+
<div class="star-2"></div>
|
1511 |
+
<div class="star-2"></div>
|
1512 |
+
<div class="star-2"></div>
|
1513 |
+
<div class="star-2"></div>
|
1514 |
+
<div class="star-2"></div>
|
1515 |
+
<div class="star-2"></div>
|
1516 |
+
<div class="star-2"></div>
|
1517 |
+
<div class="star-2"></div>
|
1518 |
+
<div class="star-2"></div>
|
1519 |
+
<div class="star-2"></div>
|
1520 |
+
<div class="star-2"></div>
|
1521 |
+
<div class="star-2"></div>
|
1522 |
+
<div class="star-2"></div>
|
1523 |
+
<div class="star-2"></div>
|
1524 |
+
<div class="star-2"></div>
|
1525 |
+
<div class="star-2"></div>
|
1526 |
+
<div class="star-2"></div>
|
1527 |
+
<div class="star-2"></div>
|
1528 |
+
<div class="star-2"></div>
|
1529 |
+
<div class="star-2"></div>
|
1530 |
+
<div class="star-2"></div>
|
1531 |
+
<div class="star-2"></div>
|
1532 |
+
<div class="star-2"></div>
|
1533 |
+
<div class="star-2"></div>
|
1534 |
+
<div class="star-2"></div>
|
1535 |
+
<div class="star-2"></div>
|
1536 |
+
</div>
|
1537 |
+
<div class="container container-bird">
|
1538 |
+
<div class="bird bird-anim">
|
1539 |
+
<div class="bird-container">
|
1540 |
+
<div class="wing wing-left">
|
1541 |
+
<div class="wing-left-top"></div>
|
1542 |
+
</div>
|
1543 |
+
<div class="wing wing-right">
|
1544 |
+
<div class="wing-right-top"></div>
|
1545 |
+
</div>
|
1546 |
+
</div>
|
1547 |
+
</div>
|
1548 |
+
<div class="bird bird-anim">
|
1549 |
+
<div class="bird-container">
|
1550 |
+
<div class="wing wing-left">
|
1551 |
+
<div class="wing-left-top"></div>
|
1552 |
+
</div>
|
1553 |
+
<div class="wing wing-right">
|
1554 |
+
<div class="wing-right-top"></div>
|
1555 |
+
</div>
|
1556 |
+
</div>
|
1557 |
+
</div>
|
1558 |
+
<div class="bird bird-anim">
|
1559 |
+
<div class="bird-container">
|
1560 |
+
<div class="wing wing-left">
|
1561 |
+
<div class="wing-left-top"></div>
|
1562 |
+
</div>
|
1563 |
+
<div class="wing wing-right">
|
1564 |
+
<div class="wing-right-top"></div>
|
1565 |
+
</div>
|
1566 |
+
</div>
|
1567 |
+
</div>
|
1568 |
+
<div class="bird bird-anim">
|
1569 |
+
<div class="bird-container">
|
1570 |
+
<div class="wing wing-left">
|
1571 |
+
<div class="wing-left-top"></div>
|
1572 |
+
</div>
|
1573 |
+
<div class="wing wing-right">
|
1574 |
+
<div class="wing-right-top"></div>
|
1575 |
+
</div>
|
1576 |
+
</div>
|
1577 |
+
</div>
|
1578 |
+
<div class="bird bird-anim">
|
1579 |
+
<div class="bird-container">
|
1580 |
+
<div class="wing wing-left">
|
1581 |
+
<div class="wing-left-top"></div>
|
1582 |
+
</div>
|
1583 |
+
<div class="wing wing-right">
|
1584 |
+
<div class="wing-right-top"></div>
|
1585 |
+
</div>
|
1586 |
+
</div>
|
1587 |
+
</div>
|
1588 |
+
<div class="bird bird-anim">
|
1589 |
+
<div class="bird-container">
|
1590 |
+
<div class="wing wing-left">
|
1591 |
+
<div class="wing-left-top"></div>
|
1592 |
+
</div>
|
1593 |
+
<div class="wing wing-right">
|
1594 |
+
<div class="wing-right-top"></div>
|
1595 |
+
</div>
|
1596 |
+
</div>
|
1597 |
+
</div>
|
1598 |
+
<div class="container-title">
|
1599 |
+
<div class="title">
|
1600 |
+
<div class="number">4</div>
|
1601 |
+
<div class="moon">
|
1602 |
+
<div class="face">
|
1603 |
+
<div class="mouth"></div>
|
1604 |
+
<div class="eyes">
|
1605 |
+
<div class="eye-left"></div>
|
1606 |
+
<div class="eye-right"></div>
|
1607 |
+
</div>
|
1608 |
+
</div>
|
1609 |
+
</div>
|
1610 |
+
<div class="number">4</div>
|
1611 |
+
</div>
|
1612 |
+
<div class="subtitle">哎呀。看来你拐错弯了。</div>
|
1613 |
+
<a href="https://www.xiaomaw.cn/"><button>返回</button></a>
|
1614 |
+
</div>
|
1615 |
+
<div style="display:none">
|
1616 |
+
<script type="text/javascript">
|
1617 |
+
var t=10;
|
1618 |
+
setInterval("refer()",500);
|
1619 |
+
function refer(){
|
1620 |
+
if(t==0){
|
1621 |
+
location.href="https://www.xiaomaw.cn/";
|
1622 |
+
}
|
1623 |
+
document.getElementById('show').innerHTML=""+t+"";
|
1624 |
+
t--;
|
1625 |
+
}
|
1626 |
+
</script>
|
1627 |
+
<span id="show"></span>
|
1628 |
+
</div>
|
1629 |
+
</div>
|
1630 |
+
|
1631 |
+
</body>
|
1632 |
+
</html>
|
Dockerfile
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:18-alpine AS base
|
2 |
+
|
3 |
+
FROM base AS deps
|
4 |
+
|
5 |
+
RUN apk add --no-cache libc6-compat
|
6 |
+
|
7 |
+
WORKDIR /app
|
8 |
+
|
9 |
+
COPY package.json yarn.lock ./
|
10 |
+
|
11 |
+
RUN yarn install
|
12 |
+
|
13 |
+
FROM base AS builder
|
14 |
+
|
15 |
+
RUN apk update && apk add --no-cache git
|
16 |
+
|
17 |
+
ENV OPENAI_API_KEY=""
|
18 |
+
ENV CODE=""
|
19 |
+
ARG DOCKER=true
|
20 |
+
|
21 |
+
WORKDIR /app
|
22 |
+
COPY --from=deps /app/node_modules ./node_modules
|
23 |
+
COPY . .
|
24 |
+
|
25 |
+
RUN yarn build
|
26 |
+
|
27 |
+
FROM base AS runner
|
28 |
+
WORKDIR /app
|
29 |
+
|
30 |
+
ENV OPENAI_API_KEY=""
|
31 |
+
ENV CODE=""
|
32 |
+
|
33 |
+
COPY --from=builder /app/public ./public
|
34 |
+
COPY --from=builder /app/.next/standalone ./
|
35 |
+
COPY --from=builder /app/.next/static ./.next/static
|
36 |
+
COPY --from=builder /app/.next/server ./.next/server
|
37 |
+
|
38 |
+
EXPOSE 3000
|
39 |
+
|
40 |
+
CMD ["node","server.js"]
|
LICENSE
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
版权所有(c)<2023><Zhang Yifei>
|
2 |
+
|
3 |
+
反996许可证版本1.0
|
4 |
+
|
5 |
+
在符合下列条件的情况下,
|
6 |
+
特此免费向任何得到本授权作品的副本(包括源代码、文件和/或相关内容,以下统称为“授权作品”
|
7 |
+
)的个人和法人实体授权:被授权个人或法人实体有权以任何目的处置授权作品,包括但不限于使
|
8 |
+
用、复制,修改,衍生利用、散布,发布和再许可:
|
9 |
+
|
10 |
+
|
11 |
+
1. 个人或法人实体必须在许可作品的每个再散布或衍生副本上包含以上版权声明和本许可证,不
|
12 |
+
得自行修改。
|
13 |
+
2. 个人或法人实体必须严格遵守与个人实际所在地或个人出生地或归化地、或法人实体注册地或
|
14 |
+
经营地(以较严格者为准)的司法管辖区所有适用的与劳动和就业相关法律、法规、规则和
|
15 |
+
标准。如果该司法管辖区没有此类法律、法规、规章和标准或其法律、法规、规章和标准不可
|
16 |
+
执行,则个人或法人实体必须遵守国际劳工标准的核心公约。
|
17 |
+
3. 个人或法人不得以任何方式诱导或强迫其全职或兼职员工或其独立承包人以口头或书面形式同
|
18 |
+
意直接或间接限制、削弱或放弃其所拥有的,受相关与劳动和就业有关的法律、法规、规则和
|
19 |
+
标准保护的权利或补救措施,无论该等书面或口头协议是否被该司法管辖区的法律所承认,该
|
20 |
+
等个人或法人实体也不得以任何方法限制其雇员或独立承包人向版权持有人或监督许可证合规
|
21 |
+
情况的有关当局报告或投诉上述违反许可证的行为的权利。
|
22 |
+
|
23 |
+
该授权作品是"按原样"提供,不做任何明示或暗示的保证,包括但不限于对适销性、特定用途适用
|
24 |
+
性和非侵权性的保证。在任何情况下,无论是在合同诉讼、侵权诉讼或其他诉讼中,版权持有人均
|
25 |
+
不承担因本软件或本软件的使用或其他交易而产生、引起或与之相关的任何索赔、损害或其他责任。
|
26 |
+
|
27 |
+
|
28 |
+
------------------------- ENGLISH ------------------------------
|
29 |
+
|
30 |
+
|
31 |
+
Copyright (c) <2023> <Zhang Yifei>
|
32 |
+
|
33 |
+
Anti 996 License Version 1.0 (Draft)
|
34 |
+
|
35 |
+
Permission is hereby granted to any individual or legal entity obtaining a copy
|
36 |
+
of this licensed work (including the source code, documentation and/or related
|
37 |
+
items, hereinafter collectively referred to as the "licensed work"), free of
|
38 |
+
charge, to deal with the licensed work for any purpose, including without
|
39 |
+
limitation, the rights to use, reproduce, modify, prepare derivative works of,
|
40 |
+
publish, distribute and sublicense the licensed work, subject to the following
|
41 |
+
conditions:
|
42 |
+
|
43 |
+
1. The individual or the legal entity must conspicuously display, without
|
44 |
+
modification, this License on each redistributed or derivative copy of the
|
45 |
+
Licensed Work.
|
46 |
+
|
47 |
+
2. The individual or the legal entity must strictly comply with all applicable
|
48 |
+
laws, regulations, rules and standards of the jurisdiction relating to
|
49 |
+
labor and employment where the individual is physically located or where
|
50 |
+
the individual was born or naturalized; or where the legal entity is
|
51 |
+
registered or is operating (whichever is stricter). In case that the
|
52 |
+
jurisdiction has no such laws, regulations, rules and standards or its
|
53 |
+
laws, regulations, rules and standards are unenforceable, the individual
|
54 |
+
or the legal entity are required to comply with Core International Labor
|
55 |
+
Standards.
|
56 |
+
|
57 |
+
3. The individual or the legal entity shall not induce or force its
|
58 |
+
employee(s), whether full-time or part-time, or its independent
|
59 |
+
contractor(s), in any methods, to agree in oral or written form,
|
60 |
+
to directly or indirectly restrict, weaken or relinquish his or
|
61 |
+
her rights or remedies under such laws, regulations, rules and
|
62 |
+
standards relating to labor and employment as mentioned above,
|
63 |
+
no matter whether such written or oral agreement are enforceable
|
64 |
+
under the laws of the said jurisdiction, nor shall such individual
|
65 |
+
or the legal entity limit, in any methods, the rights of its employee(s)
|
66 |
+
or independent contractor(s) from reporting or complaining to the copyright
|
67 |
+
holder or relevant authorities monitoring the compliance of the license
|
68 |
+
about its violation(s) of the said license.
|
69 |
+
|
70 |
+
THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
71 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
72 |
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
|
73 |
+
HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
74 |
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION
|
75 |
+
WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.
|
README.md
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div align="center">
|
2 |
+
<img src="./static/icon.svg" alt="预览"/>
|
3 |
+
|
4 |
+
<h1 align="center">ChatGPT Next Web</h1>
|
5 |
+
|
6 |
+
一键免费部署你的私人 ChatGPT 网页应用。
|
7 |
+
|
8 |
+
One-Click to deploy your own ChatGPT web UI.
|
9 |
+
|
10 |
+
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Donate](#捐赠-donate-usdt)
|
11 |
+
|
12 |
+
[![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)
|
13 |
+
|
14 |
+
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
15 |
+
|
16 |
+
![主界面](./static/cover.png)
|
17 |
+
|
18 |
+
</div>
|
19 |
+
|
20 |
+
## 主要功能
|
21 |
+
|
22 |
+
- 在 1 分钟内使用 Vercel **免费一键部署**
|
23 |
+
- 精心设计的 UI,响应式设计,支持深色模式
|
24 |
+
- 极快的首屏加载速度(~85kb)
|
25 |
+
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
26 |
+
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
27 |
+
- 一键导出聊天记录,完整的 Markdown 支持
|
28 |
+
- 拥有自己的域名?好上加好,绑定后即可在任何地方**无障碍**快速访问
|
29 |
+
|
30 |
+
## Features
|
31 |
+
|
32 |
+
- **Deploy for free with one-click** on Vercel in under 1 minute
|
33 |
+
- Responsive design, and dark mode
|
34 |
+
- Fast first screen loading speed (~85kb)
|
35 |
+
- 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)
|
36 |
+
- Automatically compresses chat history to support long conversations while also saving your tokens
|
37 |
+
- One-click export all chat history with full Markdown support
|
38 |
+
|
39 |
+
## 开发计划 Roadmap
|
40 |
+
- System Prompt: pin a user defined prompt as system prompt 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
41 |
+
- User Prompt: user can edit and save custom prompts to prompt list 允许用户自行编辑内置 Prompt 列表
|
42 |
+
- Self-host Model: support llama, alpaca, ChatGLM, BELLE etc. 支持自部署的大语言模型
|
43 |
+
- Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
44 |
+
|
45 |
+
### 不会开发的功能 Not in Plan
|
46 |
+
- User login, accounts, cloud sync 用户登陆、账号管理、消息云同步
|
47 |
+
- UI text customize 界面文字自定义
|
48 |
+
|
49 |
+
## 开始使用
|
50 |
+
|
51 |
+
1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys);
|
52 |
+
2. 点击右侧按钮开始部署:
|
53 |
+
[![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),直接使用 Github 账号登陆即可,记得在环境变量页填入 API Key;
|
54 |
+
3. 部署完毕后,即可开始使用;
|
55 |
+
4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。
|
56 |
+
|
57 |
+
## Get Started
|
58 |
+
|
59 |
+
1. Get [OpenAI API Key](https://platform.openai.com/account/api-keys);
|
60 |
+
2. Click
|
61 |
+
[![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);
|
62 |
+
3. Enjoy :)
|
63 |
+
|
64 |
+
## 保持更新 Keep Updated
|
65 |
+
|
66 |
+
如果你按照上述步骤一键部署了自己的项目,可能会发现总是提示“存在更新”的问题,这是由于 Vercel 会默认为你创建一个新项目而不是 fork 本项目,这会导致无法正确地检测更新。
|
67 |
+
推荐你按照下列步骤重新部署:
|
68 |
+
|
69 |
+
- 删除掉原先的 repo;
|
70 |
+
- fork 本项目;
|
71 |
+
- 前往 vercel 控制台,删除掉原先的 project,然后新建 project,选择你刚刚 fork 出来的项目重新进行部署即可;
|
72 |
+
- 在重新部署的过程中,请手动添加名为 `OPENAI_API_KEY` 的环境变量,并填入你的 api key 作为值。
|
73 |
+
|
74 |
+
本项目会持续更新,如果你想让代码库总是保持更新,可以查看 [Github 的文档](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) 了解如何让 fork 的项目与上游代码同步,建议定期进行同步操作以获得新功能。
|
75 |
+
|
76 |
+
你可以 star/watch 本项目或者 follow 作者来及时获得新功能更新通知。
|
77 |
+
|
78 |
+
If you have deployed your own project with just one click following the steps above, you may encounter the issue of "Updates Available" constantly showing up. This is because Vercel will create a new project for you by default instead of forking this project, resulting in the inability to detect updates correctly.
|
79 |
+
|
80 |
+
We recommend that you follow the steps below to re-deploy:
|
81 |
+
|
82 |
+
- Delete the original repo;
|
83 |
+
- Fork this project;
|
84 |
+
- Go to the Vercel dashboard, delete the original project, then create a new project and select the project you just forked to redeploy;
|
85 |
+
- Please manually add an environment variable named `OPENAI_API_KEY` and enter your API key as the value during the redeploy process.
|
86 |
+
|
87 |
+
This project will be continuously maintained. If you want to keep the code repository up to date, 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. It is recommended to perform synchronization operations regularly.
|
88 |
+
|
89 |
+
You can star or watch this project or follow author to get release notifictions in time.
|
90 |
+
|
91 |
+
## 配置密码 Password
|
92 |
+
|
93 |
+
本项目提供有限的权限控制功能,请在 Vercel 项目控制面板的环境变量页增加名为 `CODE` 的环境变量,值为用英文逗号分隔的自定义密码:
|
94 |
+
|
95 |
+
```
|
96 |
+
code1,code2,code3
|
97 |
+
```
|
98 |
+
|
99 |
+
增加或修改该环境变量后,请**重新部署**项目使改动生效。
|
100 |
+
|
101 |
+
This project provides limited access control. Please add an environment variable named `CODE` on the vercel environment variables page. The value should be passwords separated by comma like this:
|
102 |
+
|
103 |
+
```
|
104 |
+
code1,code2,code3
|
105 |
+
```
|
106 |
+
|
107 |
+
After adding or modifying this environment variable, please redeploy the project for the changes to take effect.
|
108 |
+
|
109 |
+
## 环境变量 Environment Variables
|
110 |
+
|
111 |
+
### `OPENAI_API_KEY` (required)
|
112 |
+
|
113 |
+
OpanAI 密钥。
|
114 |
+
|
115 |
+
Your openai api key.
|
116 |
+
|
117 |
+
### `CODE` (optional)
|
118 |
+
|
119 |
+
访问密码,可选,可以使用逗号隔开多个密码。
|
120 |
+
|
121 |
+
Access passsword, separated by comma.
|
122 |
+
|
123 |
+
### `BASE_URL` (optional)
|
124 |
+
|
125 |
+
> Default: `api.openai.com`
|
126 |
+
|
127 |
+
OpenAI 接口代理 URL。
|
128 |
+
|
129 |
+
Override openai api request base url.
|
130 |
+
|
131 |
+
### `PROTOCOL` (optional)
|
132 |
+
|
133 |
+
> Default: `https`
|
134 |
+
|
135 |
+
> Values: `http` | `https`
|
136 |
+
|
137 |
+
OpenAI 接口协议。
|
138 |
+
|
139 |
+
Override openai api request protocol.
|
140 |
+
|
141 |
+
## 开发 Development
|
142 |
+
|
143 |
+
点击下方按钮,开始二次开发:
|
144 |
+
|
145 |
+
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
|
146 |
+
|
147 |
+
在开始写代码之前,需要在项目根目录新建一个 `.env.local` 文件,里面填入环境变量:
|
148 |
+
|
149 |
+
Before starting development, you must create a new `.env.local` file at project root, and place your api key into it:
|
150 |
+
|
151 |
+
```
|
152 |
+
OPENAI_API_KEY=<your api key here>
|
153 |
+
```
|
154 |
+
|
155 |
+
### 本地开发 Local Development
|
156 |
+
|
157 |
+
> 如果你是中国大陆用户,不建议在本地进行开发,除非你能够独立解决 OpenAI API 本地代理问题。
|
158 |
+
|
159 |
+
1. 安装 nodejs 和 yarn,具体细节请询问 ChatGPT;
|
160 |
+
2. 执行 `yarn install && yarn dev` 即可。
|
161 |
+
|
162 |
+
### 本地部署 Local Deployment
|
163 |
+
|
164 |
+
```shell
|
165 |
+
bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
|
166 |
+
```
|
167 |
+
|
168 |
+
### 容器部署 Docker Deployment
|
169 |
+
|
170 |
+
```shell
|
171 |
+
docker pull yidadaa/chatgpt-next-web
|
172 |
+
|
173 |
+
docker run -d -p 3000:3000 -e OPENAI_API_KEY="" -e CODE="" yidadaa/chatgpt-next-web
|
174 |
+
```
|
175 |
+
|
176 |
+
## 截图 Screenshots
|
177 |
+
|
178 |
+
![设置 Settings](./static/settings.png)
|
179 |
+
|
180 |
+
![更多展示 More](./static/more.png)
|
181 |
+
|
182 |
+
|
183 |
+
## 捐赠 Donate USDT
|
184 |
+
> BNB Smart Chain (BEP 20)
|
185 |
+
```
|
186 |
+
0x67cD02c7EB62641De576a1fA3EdB32eA0c3ffD89
|
187 |
+
```
|
188 |
+
|
189 |
+
## 鸣谢 Special Thanks
|
190 |
+
|
191 |
+
### 捐赠者 Sponsor
|
192 |
+
|
193 |
+
[@mushan0x0](https://github.com/mushan0x0)
|
194 |
+
[@ClarenceDan](https://github.com/ClarenceDan)
|
195 |
+
[@zhangjia](https://github.com/zhangjia)
|
196 |
+
[@hoochanlon](https://github.com/hoochanlon)
|
197 |
+
|
198 |
+
### 贡献者 Contributor
|
199 |
+
|
200 |
+
[Contributors](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
|
201 |
+
|
202 |
+
## LICENSE
|
203 |
+
|
204 |
+
[Anti 996 License](https://github.com/kattgu7/Anti-996-License/blob/master/LICENSE_CN_EN)
|
README.txt
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
免责声明:
|
2 |
+
|
3 |
+
本站所发布的一切资源仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
|
4 |
+
|
5 |
+
附: 二○○二年一月一日《计算机软件保护条例》第十七条规定:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬!鉴于此,也希望大家按此说明研究软件!
|
6 |
+
|
7 |
+
注:本站所有资源均来自网络转载,版权归原作者和公司所有,如果有侵犯到您的权益,请第一时间联系邮箱: 770973008@qq.com 我们将配合处理!
|
8 |
+
|
9 |
+
----------------------------------------------------
|
10 |
+
|
11 |
+
版权声明:
|
12 |
+
|
13 |
+
一、本站致力于为软件爱好者提供国内外软件开发技术和软件共享,着力为用户提供优资资源。
|
14 |
+
|
15 |
+
二、本站提供的所有下载文件均为网络共享资源,请于下载后的24小时内删除。如需体验更多乐趣,还请支持正版。
|
16 |
+
|
17 |
+
三、我站提供用户下载的所有内容均转自互联网。如有内容侵犯您的版权或其他利益的,请编辑邮件并加以说明发送到站长邮箱。站长会进行审查之后,情况属实的会在三个工作日内为您删除。
|
18 |
+
|
19 |
+
----------------------------------------------------
|
20 |
+
|
21 |
+
小马博客:https://www.xiaomaw.cn
|
22 |
+
|
app/api/access.ts
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import md5 from "spark-md5";
|
2 |
+
|
3 |
+
export function getAccessCodes(): Set<string> {
|
4 |
+
const code = process.env.CODE;
|
5 |
+
|
6 |
+
try {
|
7 |
+
const codes = (code?.split(",") ?? [])
|
8 |
+
.filter((v) => !!v)
|
9 |
+
.map((v) => md5.hash(v.trim()));
|
10 |
+
return new Set(codes);
|
11 |
+
} catch (e) {
|
12 |
+
return new Set();
|
13 |
+
}
|
14 |
+
}
|
15 |
+
|
16 |
+
export const ACCESS_CODES = getAccessCodes();
|
17 |
+
export const IS_IN_DOCKER = process.env.DOCKER;
|
app/api/chat-stream/route.ts
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { createParser } from "eventsource-parser";
|
2 |
+
import { NextRequest } from "next/server";
|
3 |
+
import { requestOpenai } from "../common";
|
4 |
+
|
5 |
+
async function createStream(req: NextRequest) {
|
6 |
+
const encoder = new TextEncoder();
|
7 |
+
const decoder = new TextDecoder();
|
8 |
+
|
9 |
+
const res = await requestOpenai(req);
|
10 |
+
|
11 |
+
const stream = new ReadableStream({
|
12 |
+
async start(controller) {
|
13 |
+
function onParse(event: any) {
|
14 |
+
if (event.type === "event") {
|
15 |
+
const data = event.data;
|
16 |
+
// https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
|
17 |
+
if (data === "[DONE]") {
|
18 |
+
controller.close();
|
19 |
+
return;
|
20 |
+
}
|
21 |
+
try {
|
22 |
+
const json = JSON.parse(data);
|
23 |
+
const text = json.choices[0].delta.content;
|
24 |
+
const queue = encoder.encode(text);
|
25 |
+
controller.enqueue(queue);
|
26 |
+
} catch (e) {
|
27 |
+
controller.error(e);
|
28 |
+
}
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
const parser = createParser(onParse);
|
33 |
+
for await (const chunk of res.body as any) {
|
34 |
+
parser.feed(decoder.decode(chunk));
|
35 |
+
}
|
36 |
+
},
|
37 |
+
});
|
38 |
+
return stream;
|
39 |
+
}
|
40 |
+
|
41 |
+
export async function POST(req: NextRequest) {
|
42 |
+
try {
|
43 |
+
const stream = await createStream(req);
|
44 |
+
return new Response(stream);
|
45 |
+
} catch (error) {
|
46 |
+
console.error("[Chat Stream]", error);
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
export const config = {
|
51 |
+
runtime: "edge",
|
52 |
+
};
|
app/api/common.ts
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest } from "next/server";
|
2 |
+
|
3 |
+
const OPENAI_URL = "api.openai.com";
|
4 |
+
const DEFAULT_PROTOCOL = "https";
|
5 |
+
const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
|
6 |
+
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
7 |
+
|
8 |
+
export async function requestOpenai(req: NextRequest) {
|
9 |
+
const apiKey = req.headers.get("token");
|
10 |
+
const openaiPath = req.headers.get("path");
|
11 |
+
|
12 |
+
console.log("[Proxy] ", openaiPath);
|
13 |
+
|
14 |
+
return fetch(`${PROTOCOL}://${BASE_URL}/${openaiPath}`, {
|
15 |
+
headers: {
|
16 |
+
"Content-Type": "application/json",
|
17 |
+
Authorization: `Bearer ${apiKey}`,
|
18 |
+
},
|
19 |
+
method: req.method,
|
20 |
+
body: req.body,
|
21 |
+
});
|
22 |
+
}
|
app/api/openai/route.ts
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest, NextResponse } from "next/server";
|
2 |
+
import { requestOpenai } from "../common";
|
3 |
+
|
4 |
+
async function makeRequest(req: NextRequest) {
|
5 |
+
try {
|
6 |
+
const res = await requestOpenai(req);
|
7 |
+
return new Response(res.body);
|
8 |
+
} catch (e) {
|
9 |
+
console.error("[OpenAI] ", req.body, e);
|
10 |
+
return NextResponse.json(
|
11 |
+
{
|
12 |
+
error: true,
|
13 |
+
msg: JSON.stringify(e),
|
14 |
+
},
|
15 |
+
{
|
16 |
+
status: 500,
|
17 |
+
},
|
18 |
+
);
|
19 |
+
}
|
20 |
+
}
|
21 |
+
|
22 |
+
export async function POST(req: NextRequest) {
|
23 |
+
return makeRequest(req);
|
24 |
+
}
|
25 |
+
|
26 |
+
export async function GET(req: NextRequest) {
|
27 |
+
return makeRequest(req);
|
28 |
+
}
|
app/api/openai/typing.ts
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type {
|
2 |
+
CreateChatCompletionRequest,
|
3 |
+
CreateChatCompletionResponse,
|
4 |
+
} from "openai";
|
5 |
+
|
6 |
+
export type ChatRequest = CreateChatCompletionRequest;
|
7 |
+
export type ChatReponse = CreateChatCompletionResponse;
|
app/components/button.module.scss
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.icon-button {
|
2 |
+
background-color: var(--white);
|
3 |
+
border-radius: 10px;
|
4 |
+
display: flex;
|
5 |
+
align-items: center;
|
6 |
+
justify-content: center;
|
7 |
+
padding: 10px;
|
8 |
+
|
9 |
+
box-shadow: var(--card-shadow);
|
10 |
+
cursor: pointer;
|
11 |
+
transition: all 0.3s ease;
|
12 |
+
overflow: hidden;
|
13 |
+
user-select: none;
|
14 |
+
}
|
15 |
+
|
16 |
+
.border {
|
17 |
+
border: var(--border-in-light);
|
18 |
+
}
|
19 |
+
|
20 |
+
.icon-button:hover {
|
21 |
+
filter: brightness(0.9);
|
22 |
+
border-color: var(--primary);
|
23 |
+
}
|
24 |
+
|
25 |
+
.icon-button-icon {
|
26 |
+
width: 16px;
|
27 |
+
height: 16px;
|
28 |
+
display: flex;
|
29 |
+
justify-content: center;
|
30 |
+
align-items: center;
|
31 |
+
}
|
32 |
+
|
33 |
+
@media only screen and (max-width: 600px) {
|
34 |
+
.icon-button {
|
35 |
+
padding: 16px;
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
@mixin dark-button {
|
40 |
+
div:not(:global(.no-dark))>.icon-button-icon {
|
41 |
+
filter: invert(0.5);
|
42 |
+
}
|
43 |
+
|
44 |
+
.icon-button:hover {
|
45 |
+
filter: brightness(1.2);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
:global(.dark) {
|
50 |
+
@include dark-button;
|
51 |
+
}
|
52 |
+
|
53 |
+
@media (prefers-color-scheme: dark) {
|
54 |
+
@include dark-button;
|
55 |
+
}
|
56 |
+
|
57 |
+
.icon-button-text {
|
58 |
+
margin-left: 5px;
|
59 |
+
font-size: 12px;
|
60 |
+
}
|
app/components/button.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
|
3 |
+
import styles from "./button.module.scss";
|
4 |
+
|
5 |
+
export function IconButton(props: {
|
6 |
+
onClick?: () => void;
|
7 |
+
icon: JSX.Element;
|
8 |
+
text?: string;
|
9 |
+
bordered?: boolean;
|
10 |
+
className?: string;
|
11 |
+
title?: string;
|
12 |
+
}) {
|
13 |
+
return (
|
14 |
+
<div
|
15 |
+
className={
|
16 |
+
styles["icon-button"] +
|
17 |
+
` ${props.bordered && styles.border} ${props.className ?? ""}`
|
18 |
+
}
|
19 |
+
onClick={props.onClick}
|
20 |
+
title={props.title}
|
21 |
+
>
|
22 |
+
<div className={styles["icon-button-icon"]}>{props.icon}</div>
|
23 |
+
{props.text && (
|
24 |
+
<div className={styles["icon-button-text"]}>{props.text}</div>
|
25 |
+
)}
|
26 |
+
</div>
|
27 |
+
);
|
28 |
+
}
|
app/components/home.module.scss
ADDED
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "./window.scss";
|
2 |
+
|
3 |
+
@mixin container {
|
4 |
+
background-color: var(--white);
|
5 |
+
border: var(--border-in-light);
|
6 |
+
border-radius: 20px;
|
7 |
+
box-shadow: var(--shadow);
|
8 |
+
color: var(--black);
|
9 |
+
background-color: var(--white);
|
10 |
+
min-width: 600px;
|
11 |
+
min-height: 480px;
|
12 |
+
max-width: 900px;
|
13 |
+
|
14 |
+
display: flex;
|
15 |
+
overflow: hidden;
|
16 |
+
box-sizing: border-box;
|
17 |
+
|
18 |
+
width: var(--window-width);
|
19 |
+
height: var(--window-height);
|
20 |
+
}
|
21 |
+
|
22 |
+
.container {
|
23 |
+
@include container();
|
24 |
+
}
|
25 |
+
|
26 |
+
@media only screen and (min-width: 600px) {
|
27 |
+
.tight-container {
|
28 |
+
--window-width: 100vw;
|
29 |
+
--window-height: var(--full-height);
|
30 |
+
--window-content-width: calc(100% - var(--sidebar-width));
|
31 |
+
|
32 |
+
@include container();
|
33 |
+
|
34 |
+
max-width: 100vw;
|
35 |
+
max-height: var(--full-height);
|
36 |
+
|
37 |
+
border-radius: 0;
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
.sidebar {
|
42 |
+
top: 0;
|
43 |
+
width: var(--sidebar-width);
|
44 |
+
box-sizing: border-box;
|
45 |
+
padding: 20px;
|
46 |
+
background-color: var(--second);
|
47 |
+
display: flex;
|
48 |
+
flex-direction: column;
|
49 |
+
box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05);
|
50 |
+
}
|
51 |
+
|
52 |
+
.window-content {
|
53 |
+
width: var(--window-content-width);
|
54 |
+
height: 100%;
|
55 |
+
display: flex;
|
56 |
+
flex-direction: column;
|
57 |
+
}
|
58 |
+
|
59 |
+
.mobile {
|
60 |
+
display: none;
|
61 |
+
}
|
62 |
+
|
63 |
+
@media only screen and (max-width: 600px) {
|
64 |
+
.container {
|
65 |
+
min-height: unset;
|
66 |
+
min-width: unset;
|
67 |
+
max-height: unset;
|
68 |
+
min-width: unset;
|
69 |
+
border: 0;
|
70 |
+
border-radius: 0;
|
71 |
+
}
|
72 |
+
|
73 |
+
.sidebar {
|
74 |
+
position: absolute;
|
75 |
+
left: -100%;
|
76 |
+
z-index: 999;
|
77 |
+
height: var(--full-height);
|
78 |
+
transition: all ease 0.3s;
|
79 |
+
box-shadow: none;
|
80 |
+
}
|
81 |
+
|
82 |
+
.sidebar-show {
|
83 |
+
left: 0;
|
84 |
+
}
|
85 |
+
|
86 |
+
.mobile {
|
87 |
+
display: block;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
.sidebar-header {
|
92 |
+
position: relative;
|
93 |
+
padding-top: 20px;
|
94 |
+
padding-bottom: 20px;
|
95 |
+
}
|
96 |
+
|
97 |
+
.sidebar-logo {
|
98 |
+
position: absolute;
|
99 |
+
right: 0;
|
100 |
+
bottom: 18px;
|
101 |
+
}
|
102 |
+
|
103 |
+
.sidebar-title {
|
104 |
+
font-size: 20px;
|
105 |
+
font-weight: bold;
|
106 |
+
}
|
107 |
+
|
108 |
+
.sidebar-sub-title {
|
109 |
+
font-size: 12px;
|
110 |
+
font-weight: 400px;
|
111 |
+
}
|
112 |
+
|
113 |
+
.sidebar-body {
|
114 |
+
flex: 1;
|
115 |
+
overflow: auto;
|
116 |
+
}
|
117 |
+
|
118 |
+
.chat-list {
|
119 |
+
}
|
120 |
+
|
121 |
+
.chat-item {
|
122 |
+
padding: 10px 14px;
|
123 |
+
background-color: var(--white);
|
124 |
+
border-radius: 10px;
|
125 |
+
margin-bottom: 10px;
|
126 |
+
box-shadow: var(--card-shadow);
|
127 |
+
transition: all 0.3s ease;
|
128 |
+
cursor: pointer;
|
129 |
+
user-select: none;
|
130 |
+
border: 2px solid transparent;
|
131 |
+
position: relative;
|
132 |
+
overflow: hidden;
|
133 |
+
}
|
134 |
+
|
135 |
+
@keyframes slide-in {
|
136 |
+
from {
|
137 |
+
opacity: 0;
|
138 |
+
transform: translateY(20px);
|
139 |
+
}
|
140 |
+
|
141 |
+
to {
|
142 |
+
opacity: 1;
|
143 |
+
transform: translateY(0px);
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
.chat-item:hover {
|
148 |
+
background-color: var(--hover-color);
|
149 |
+
}
|
150 |
+
|
151 |
+
.chat-item-selected {
|
152 |
+
border-color: var(--primary);
|
153 |
+
}
|
154 |
+
|
155 |
+
.chat-item-title {
|
156 |
+
font-size: 14px;
|
157 |
+
font-weight: bolder;
|
158 |
+
display: block;
|
159 |
+
width: 200px;
|
160 |
+
overflow: hidden;
|
161 |
+
text-overflow: ellipsis;
|
162 |
+
white-space: nowrap;
|
163 |
+
}
|
164 |
+
|
165 |
+
.chat-item-delete {
|
166 |
+
position: absolute;
|
167 |
+
top: 10px;
|
168 |
+
right: -20px;
|
169 |
+
transition: all ease 0.3s;
|
170 |
+
opacity: 0;
|
171 |
+
}
|
172 |
+
|
173 |
+
.chat-item:hover > .chat-item-delete {
|
174 |
+
opacity: 0.5;
|
175 |
+
right: 10px;
|
176 |
+
}
|
177 |
+
|
178 |
+
.chat-item:hover > .chat-item-delete:hover {
|
179 |
+
opacity: 1;
|
180 |
+
}
|
181 |
+
|
182 |
+
.chat-item-info {
|
183 |
+
display: flex;
|
184 |
+
justify-content: space-between;
|
185 |
+
color: rgb(166, 166, 166);
|
186 |
+
font-size: 12px;
|
187 |
+
margin-top: 8px;
|
188 |
+
}
|
189 |
+
|
190 |
+
.chat-item-count {
|
191 |
+
}
|
192 |
+
|
193 |
+
.chat-item-date {
|
194 |
+
}
|
195 |
+
|
196 |
+
.sidebar-tail {
|
197 |
+
display: flex;
|
198 |
+
justify-content: space-between;
|
199 |
+
padding-top: 20px;
|
200 |
+
}
|
201 |
+
|
202 |
+
.sidebar-actions {
|
203 |
+
display: inline-flex;
|
204 |
+
}
|
205 |
+
|
206 |
+
.sidebar-action:not(:last-child) {
|
207 |
+
margin-right: 15px;
|
208 |
+
}
|
209 |
+
|
210 |
+
.chat {
|
211 |
+
display: flex;
|
212 |
+
flex-direction: column;
|
213 |
+
position: relative;
|
214 |
+
height: 100%;
|
215 |
+
}
|
216 |
+
|
217 |
+
.chat-body {
|
218 |
+
flex: 1;
|
219 |
+
overflow: auto;
|
220 |
+
padding: 20px;
|
221 |
+
margin-bottom: 100px;
|
222 |
+
}
|
223 |
+
|
224 |
+
.chat-body-title {
|
225 |
+
cursor: pointer;
|
226 |
+
|
227 |
+
&:hover {
|
228 |
+
text-decoration: underline;
|
229 |
+
}
|
230 |
+
}
|
231 |
+
|
232 |
+
.chat-message {
|
233 |
+
display: flex;
|
234 |
+
flex-direction: row;
|
235 |
+
}
|
236 |
+
|
237 |
+
.chat-message-user {
|
238 |
+
display: flex;
|
239 |
+
flex-direction: row-reverse;
|
240 |
+
}
|
241 |
+
|
242 |
+
.chat-message-container {
|
243 |
+
max-width: var(--message-max-width);
|
244 |
+
display: flex;
|
245 |
+
flex-direction: column;
|
246 |
+
align-items: flex-start;
|
247 |
+
animation: slide-in ease 0.3s;
|
248 |
+
|
249 |
+
&:hover {
|
250 |
+
.chat-message-top-actions {
|
251 |
+
opacity: 1;
|
252 |
+
right: 10px;
|
253 |
+
pointer-events: all;
|
254 |
+
}
|
255 |
+
}
|
256 |
+
}
|
257 |
+
|
258 |
+
.chat-message-user > .chat-message-container {
|
259 |
+
align-items: flex-end;
|
260 |
+
}
|
261 |
+
|
262 |
+
.chat-message-avatar {
|
263 |
+
margin-top: 20px;
|
264 |
+
}
|
265 |
+
|
266 |
+
.chat-message-status {
|
267 |
+
font-size: 12px;
|
268 |
+
color: #aaa;
|
269 |
+
line-height: 1.5;
|
270 |
+
margin-top: 5px;
|
271 |
+
}
|
272 |
+
|
273 |
+
.user-avtar {
|
274 |
+
height: 30px;
|
275 |
+
width: 30px;
|
276 |
+
display: flex;
|
277 |
+
align-items: center;
|
278 |
+
justify-content: center;
|
279 |
+
border: var(--border-in-light);
|
280 |
+
box-shadow: var(--card-shadow);
|
281 |
+
border-radius: 10px;
|
282 |
+
}
|
283 |
+
|
284 |
+
.chat-message-item {
|
285 |
+
box-sizing: border-box;
|
286 |
+
max-width: 100%;
|
287 |
+
margin-top: 10px;
|
288 |
+
border-radius: 10px;
|
289 |
+
background-color: rgba(0, 0, 0, 0.05);
|
290 |
+
padding: 10px;
|
291 |
+
font-size: 14px;
|
292 |
+
user-select: text;
|
293 |
+
word-break: break-word;
|
294 |
+
border: var(--border-in-light);
|
295 |
+
position: relative;
|
296 |
+
}
|
297 |
+
|
298 |
+
.chat-message-top-actions {
|
299 |
+
font-size: 12px;
|
300 |
+
position: absolute;
|
301 |
+
right: 20px;
|
302 |
+
top: -26px;
|
303 |
+
left: 100px;
|
304 |
+
transition: all ease 0.3s;
|
305 |
+
opacity: 0;
|
306 |
+
pointer-events: none;
|
307 |
+
|
308 |
+
display: flex;
|
309 |
+
flex-direction: row-reverse;
|
310 |
+
|
311 |
+
.chat-message-top-action {
|
312 |
+
opacity: 0.5;
|
313 |
+
color: var(--black);
|
314 |
+
white-space: nowrap;
|
315 |
+
cursor: pointer;
|
316 |
+
|
317 |
+
&:hover {
|
318 |
+
opacity: 1;
|
319 |
+
}
|
320 |
+
|
321 |
+
&:not(:first-child) {
|
322 |
+
margin-right: 10px;
|
323 |
+
}
|
324 |
+
}
|
325 |
+
}
|
326 |
+
|
327 |
+
.chat-message-user > .chat-message-container > .chat-message-item {
|
328 |
+
background-color: var(--second);
|
329 |
+
}
|
330 |
+
|
331 |
+
.chat-message-actions {
|
332 |
+
display: flex;
|
333 |
+
flex-direction: row-reverse;
|
334 |
+
width: 100%;
|
335 |
+
padding-top: 5px;
|
336 |
+
box-sizing: border-box;
|
337 |
+
font-size: 12px;
|
338 |
+
}
|
339 |
+
|
340 |
+
.chat-message-action-date {
|
341 |
+
color: #aaa;
|
342 |
+
}
|
343 |
+
|
344 |
+
.chat-input-panel {
|
345 |
+
position: absolute;
|
346 |
+
bottom: 0px;
|
347 |
+
display: flex;
|
348 |
+
width: 100%;
|
349 |
+
padding: 20px;
|
350 |
+
box-sizing: border-box;
|
351 |
+
flex-direction: column;
|
352 |
+
}
|
353 |
+
|
354 |
+
@mixin single-line {
|
355 |
+
white-space: nowrap;
|
356 |
+
overflow: hidden;
|
357 |
+
text-overflow: ellipsis;
|
358 |
+
}
|
359 |
+
|
360 |
+
.prompt-hints {
|
361 |
+
min-height: 20px;
|
362 |
+
width: 100%;
|
363 |
+
max-height: 50vh;
|
364 |
+
overflow: auto;
|
365 |
+
display: flex;
|
366 |
+
flex-direction: column-reverse;
|
367 |
+
|
368 |
+
background-color: var(--white);
|
369 |
+
border: var(--border-in-light);
|
370 |
+
border-radius: 10px;
|
371 |
+
margin-bottom: 10px;
|
372 |
+
box-shadow: var(--shadow);
|
373 |
+
|
374 |
+
.prompt-hint {
|
375 |
+
color: var(--black);
|
376 |
+
padding: 6px 10px;
|
377 |
+
animation: slide-in ease 0.3s;
|
378 |
+
cursor: pointer;
|
379 |
+
transition: all ease 0.3s;
|
380 |
+
border: transparent 1px solid;
|
381 |
+
margin: 4px;
|
382 |
+
border-radius: 8px;
|
383 |
+
|
384 |
+
&:not(:last-child) {
|
385 |
+
margin-top: 0;
|
386 |
+
}
|
387 |
+
|
388 |
+
.hint-title {
|
389 |
+
font-size: 12px;
|
390 |
+
font-weight: bolder;
|
391 |
+
|
392 |
+
@include single-line();
|
393 |
+
}
|
394 |
+
.hint-content {
|
395 |
+
font-size: 12px;
|
396 |
+
|
397 |
+
@include single-line();
|
398 |
+
}
|
399 |
+
|
400 |
+
&-selected,
|
401 |
+
&:hover {
|
402 |
+
border-color: var(--primary);
|
403 |
+
}
|
404 |
+
}
|
405 |
+
}
|
406 |
+
|
407 |
+
.chat-input-panel-inner {
|
408 |
+
display: flex;
|
409 |
+
flex: 1;
|
410 |
+
}
|
411 |
+
|
412 |
+
.chat-input {
|
413 |
+
height: 100%;
|
414 |
+
width: 100%;
|
415 |
+
border-radius: 10px;
|
416 |
+
border: var(--border-in-light);
|
417 |
+
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
|
418 |
+
background-color: var(--white);
|
419 |
+
color: var(--black);
|
420 |
+
font-family: inherit;
|
421 |
+
padding: 10px 14px 50px;
|
422 |
+
resize: none;
|
423 |
+
outline: none;
|
424 |
+
}
|
425 |
+
|
426 |
+
@media only screen and (max-width: 600px) {
|
427 |
+
.chat-input {
|
428 |
+
font-size: 16px;
|
429 |
+
}
|
430 |
+
}
|
431 |
+
|
432 |
+
.chat-input:focus {
|
433 |
+
border: 1px solid var(--primary);
|
434 |
+
}
|
435 |
+
|
436 |
+
.chat-input-send {
|
437 |
+
background-color: var(--primary);
|
438 |
+
color: white;
|
439 |
+
|
440 |
+
position: absolute;
|
441 |
+
right: 30px;
|
442 |
+
bottom: 30px;
|
443 |
+
}
|
444 |
+
|
445 |
+
.export-content {
|
446 |
+
white-space: break-spaces;
|
447 |
+
}
|
448 |
+
|
449 |
+
.loading-content {
|
450 |
+
display: flex;
|
451 |
+
flex-direction: column;
|
452 |
+
justify-content: center;
|
453 |
+
align-items: center;
|
454 |
+
height: 100%;
|
455 |
+
width: 100%;
|
456 |
+
}
|
app/components/home.tsx
ADDED
@@ -0,0 +1,707 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import { useState, useRef, useEffect, useLayoutEffect } from "react";
|
4 |
+
import { useDebouncedCallback } from "use-debounce";
|
5 |
+
|
6 |
+
import { IconButton } from "./button";
|
7 |
+
import styles from "./home.module.scss";
|
8 |
+
|
9 |
+
import SettingsIcon from "../icons/settings.svg";
|
10 |
+
import GithubIcon from "../icons/github.svg";
|
11 |
+
import ChatGptIcon from "../icons/chatgpt.svg";
|
12 |
+
import SendWhiteIcon from "../icons/send-white.svg";
|
13 |
+
import BrainIcon from "../icons/brain.svg";
|
14 |
+
import ExportIcon from "../icons/export.svg";
|
15 |
+
import BotIcon from "../icons/bot.svg";
|
16 |
+
import AddIcon from "../icons/add.svg";
|
17 |
+
import DeleteIcon from "../icons/delete.svg";
|
18 |
+
import LoadingIcon from "../icons/three-dots.svg";
|
19 |
+
import MenuIcon from "../icons/menu.svg";
|
20 |
+
import CloseIcon from "../icons/close.svg";
|
21 |
+
import CopyIcon from "../icons/copy.svg";
|
22 |
+
import DownloadIcon from "../icons/download.svg";
|
23 |
+
|
24 |
+
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
25 |
+
import { showModal, showToast } from "./ui-lib";
|
26 |
+
import {
|
27 |
+
copyToClipboard,
|
28 |
+
downloadAs,
|
29 |
+
isIOS,
|
30 |
+
isMobileScreen,
|
31 |
+
selectOrCopy,
|
32 |
+
} from "../utils";
|
33 |
+
import Locale from "../locales";
|
34 |
+
|
35 |
+
import dynamic from "next/dynamic";
|
36 |
+
import { REPO_URL } from "../constant";
|
37 |
+
import { ControllerPool } from "../requests";
|
38 |
+
import { Prompt, usePromptStore } from "../store/prompt";
|
39 |
+
|
40 |
+
export function Loading(props: { noLogo?: boolean }) {
|
41 |
+
return (
|
42 |
+
<div className={styles["loading-content"]}>
|
43 |
+
{!props.noLogo && <BotIcon />}
|
44 |
+
<LoadingIcon />
|
45 |
+
</div>
|
46 |
+
);
|
47 |
+
}
|
48 |
+
|
49 |
+
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
50 |
+
loading: () => <LoadingIcon />,
|
51 |
+
});
|
52 |
+
|
53 |
+
const Settings = dynamic(async () => (await import("./settings")).Settings, {
|
54 |
+
loading: () => <Loading noLogo />,
|
55 |
+
});
|
56 |
+
|
57 |
+
const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
|
58 |
+
loading: () => <LoadingIcon />,
|
59 |
+
});
|
60 |
+
|
61 |
+
export function Avatar(props: { role: Message["role"] }) {
|
62 |
+
const config = useChatStore((state) => state.config);
|
63 |
+
|
64 |
+
if (props.role === "assistant") {
|
65 |
+
return <BotIcon className={styles["user-avtar"]} />;
|
66 |
+
}
|
67 |
+
|
68 |
+
return (
|
69 |
+
<div className={styles["user-avtar"]}>
|
70 |
+
<Emoji unified={config.avatar} size={18} />
|
71 |
+
</div>
|
72 |
+
);
|
73 |
+
}
|
74 |
+
|
75 |
+
export function ChatItem(props: {
|
76 |
+
onClick?: () => void;
|
77 |
+
onDelete?: () => void;
|
78 |
+
title: string;
|
79 |
+
count: number;
|
80 |
+
time: string;
|
81 |
+
selected: boolean;
|
82 |
+
}) {
|
83 |
+
return (
|
84 |
+
<div
|
85 |
+
className={`${styles["chat-item"]} ${
|
86 |
+
props.selected && styles["chat-item-selected"]
|
87 |
+
}`}
|
88 |
+
onClick={props.onClick}
|
89 |
+
>
|
90 |
+
<div className={styles["chat-item-title"]}>{props.title}</div>
|
91 |
+
<div className={styles["chat-item-info"]}>
|
92 |
+
<div className={styles["chat-item-count"]}>
|
93 |
+
{Locale.ChatItem.ChatItemCount(props.count)}
|
94 |
+
</div>
|
95 |
+
<div className={styles["chat-item-date"]}>{props.time}</div>
|
96 |
+
</div>
|
97 |
+
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
98 |
+
<DeleteIcon />
|
99 |
+
</div>
|
100 |
+
</div>
|
101 |
+
);
|
102 |
+
}
|
103 |
+
|
104 |
+
export function ChatList() {
|
105 |
+
const [sessions, selectedIndex, selectSession, removeSession] = useChatStore(
|
106 |
+
(state) => [
|
107 |
+
state.sessions,
|
108 |
+
state.currentSessionIndex,
|
109 |
+
state.selectSession,
|
110 |
+
state.removeSession,
|
111 |
+
],
|
112 |
+
);
|
113 |
+
|
114 |
+
return (
|
115 |
+
<div className={styles["chat-list"]}>
|
116 |
+
{sessions.map((item, i) => (
|
117 |
+
<ChatItem
|
118 |
+
title={item.topic}
|
119 |
+
time={item.lastUpdate}
|
120 |
+
count={item.messages.length}
|
121 |
+
key={i}
|
122 |
+
selected={i === selectedIndex}
|
123 |
+
onClick={() => selectSession(i)}
|
124 |
+
onDelete={() => removeSession(i)}
|
125 |
+
/>
|
126 |
+
))}
|
127 |
+
</div>
|
128 |
+
);
|
129 |
+
}
|
130 |
+
|
131 |
+
function useSubmitHandler() {
|
132 |
+
const config = useChatStore((state) => state.config);
|
133 |
+
const submitKey = config.submitKey;
|
134 |
+
|
135 |
+
const shouldSubmit = (e: KeyboardEvent) => {
|
136 |
+
if (e.key !== "Enter") return false;
|
137 |
+
|
138 |
+
return (
|
139 |
+
(config.submitKey === SubmitKey.AltEnter && e.altKey) ||
|
140 |
+
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
|
141 |
+
(config.submitKey === SubmitKey.ShiftEnter && e.shiftKey) ||
|
142 |
+
(config.submitKey === SubmitKey.MetaEnter && e.metaKey) ||
|
143 |
+
(config.submitKey === SubmitKey.Enter &&
|
144 |
+
!e.altKey &&
|
145 |
+
!e.ctrlKey &&
|
146 |
+
!e.shiftKey &&
|
147 |
+
!e.metaKey)
|
148 |
+
);
|
149 |
+
};
|
150 |
+
|
151 |
+
return {
|
152 |
+
submitKey,
|
153 |
+
shouldSubmit,
|
154 |
+
};
|
155 |
+
}
|
156 |
+
|
157 |
+
export function PromptHints(props: {
|
158 |
+
prompts: Prompt[];
|
159 |
+
onPromptSelect: (prompt: Prompt) => void;
|
160 |
+
}) {
|
161 |
+
if (props.prompts.length === 0) return null;
|
162 |
+
|
163 |
+
return (
|
164 |
+
<div className={styles["prompt-hints"]}>
|
165 |
+
{props.prompts.map((prompt, i) => (
|
166 |
+
<div
|
167 |
+
className={styles["prompt-hint"]}
|
168 |
+
key={prompt.title + i.toString()}
|
169 |
+
onClick={() => props.onPromptSelect(prompt)}
|
170 |
+
>
|
171 |
+
<div className={styles["hint-title"]}>{prompt.title}</div>
|
172 |
+
<div className={styles["hint-content"]}>{prompt.content}</div>
|
173 |
+
</div>
|
174 |
+
))}
|
175 |
+
</div>
|
176 |
+
);
|
177 |
+
}
|
178 |
+
|
179 |
+
export function Chat(props: {
|
180 |
+
showSideBar?: () => void;
|
181 |
+
sideBarShowing?: boolean;
|
182 |
+
}) {
|
183 |
+
type RenderMessage = Message & { preview?: boolean };
|
184 |
+
|
185 |
+
const chatStore = useChatStore();
|
186 |
+
const [session, sessionIndex] = useChatStore((state) => [
|
187 |
+
state.currentSession(),
|
188 |
+
state.currentSessionIndex,
|
189 |
+
]);
|
190 |
+
const fontSize = useChatStore((state) => state.config.fontSize);
|
191 |
+
|
192 |
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
193 |
+
const [userInput, setUserInput] = useState("");
|
194 |
+
const [isLoading, setIsLoading] = useState(false);
|
195 |
+
const { submitKey, shouldSubmit } = useSubmitHandler();
|
196 |
+
|
197 |
+
// prompt hints
|
198 |
+
const promptStore = usePromptStore();
|
199 |
+
const [promptHints, setPromptHints] = useState<Prompt[]>([]);
|
200 |
+
const onSearch = useDebouncedCallback(
|
201 |
+
(text: string) => {
|
202 |
+
setPromptHints(promptStore.search(text));
|
203 |
+
},
|
204 |
+
100,
|
205 |
+
{ leading: true, trailing: true },
|
206 |
+
);
|
207 |
+
|
208 |
+
const onPromptSelect = (prompt: Prompt) => {
|
209 |
+
setUserInput(prompt.content);
|
210 |
+
setPromptHints([]);
|
211 |
+
inputRef.current?.focus();
|
212 |
+
};
|
213 |
+
|
214 |
+
const scrollInput = () => {
|
215 |
+
const dom = inputRef.current;
|
216 |
+
if (!dom) return;
|
217 |
+
const paddingBottomNum: number = parseInt(
|
218 |
+
window.getComputedStyle(dom).paddingBottom,
|
219 |
+
10,
|
220 |
+
);
|
221 |
+
dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
|
222 |
+
};
|
223 |
+
|
224 |
+
// only search prompts when user input is short
|
225 |
+
const SEARCH_TEXT_LIMIT = 30;
|
226 |
+
const onInput = (text: string) => {
|
227 |
+
scrollInput();
|
228 |
+
setUserInput(text);
|
229 |
+
const n = text.trim().length;
|
230 |
+
|
231 |
+
// clear search results
|
232 |
+
if (n === 0) {
|
233 |
+
setPromptHints([]);
|
234 |
+
} else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
|
235 |
+
// check if need to trigger auto completion
|
236 |
+
if (text.startsWith("/") && text.length > 1) {
|
237 |
+
onSearch(text.slice(1));
|
238 |
+
}
|
239 |
+
}
|
240 |
+
};
|
241 |
+
|
242 |
+
// submit user input
|
243 |
+
const onUserSubmit = () => {
|
244 |
+
if (userInput.length <= 0) return;
|
245 |
+
setIsLoading(true);
|
246 |
+
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
247 |
+
setUserInput("");
|
248 |
+
setPromptHints([]);
|
249 |
+
inputRef.current?.focus();
|
250 |
+
};
|
251 |
+
|
252 |
+
// stop response
|
253 |
+
const onUserStop = (messageIndex: number) => {
|
254 |
+
console.log(ControllerPool, sessionIndex, messageIndex);
|
255 |
+
ControllerPool.stop(sessionIndex, messageIndex);
|
256 |
+
};
|
257 |
+
|
258 |
+
// check if should send message
|
259 |
+
const onInputKeyDown = (e: KeyboardEvent) => {
|
260 |
+
if (shouldSubmit(e)) {
|
261 |
+
onUserSubmit();
|
262 |
+
e.preventDefault();
|
263 |
+
}
|
264 |
+
};
|
265 |
+
const onRightClick = (e: any, message: Message) => {
|
266 |
+
// auto fill user input
|
267 |
+
if (message.role === "user") {
|
268 |
+
setUserInput(message.content);
|
269 |
+
}
|
270 |
+
|
271 |
+
// copy to clipboard
|
272 |
+
if (selectOrCopy(e.currentTarget, message.content)) {
|
273 |
+
e.preventDefault();
|
274 |
+
}
|
275 |
+
};
|
276 |
+
|
277 |
+
const onResend = (botIndex: number) => {
|
278 |
+
// find last user input message and resend
|
279 |
+
for (let i = botIndex; i >= 0; i -= 1) {
|
280 |
+
if (messages[i].role === "user") {
|
281 |
+
setIsLoading(true);
|
282 |
+
chatStore
|
283 |
+
.onUserInput(messages[i].content)
|
284 |
+
.then(() => setIsLoading(false));
|
285 |
+
inputRef.current?.focus();
|
286 |
+
return;
|
287 |
+
}
|
288 |
+
}
|
289 |
+
};
|
290 |
+
|
291 |
+
// for auto-scroll
|
292 |
+
const latestMessageRef = useRef<HTMLDivElement>(null);
|
293 |
+
const [autoScroll, setAutoScroll] = useState(true);
|
294 |
+
|
295 |
+
// preview messages
|
296 |
+
const messages = (session.messages as RenderMessage[])
|
297 |
+
.concat(
|
298 |
+
isLoading
|
299 |
+
? [
|
300 |
+
{
|
301 |
+
role: "assistant",
|
302 |
+
content: "……",
|
303 |
+
date: new Date().toLocaleString(),
|
304 |
+
preview: true,
|
305 |
+
},
|
306 |
+
]
|
307 |
+
: [],
|
308 |
+
)
|
309 |
+
.concat(
|
310 |
+
userInput.length > 0
|
311 |
+
? [
|
312 |
+
{
|
313 |
+
role: "user",
|
314 |
+
content: userInput,
|
315 |
+
date: new Date().toLocaleString(),
|
316 |
+
preview: true,
|
317 |
+
},
|
318 |
+
]
|
319 |
+
: [],
|
320 |
+
);
|
321 |
+
|
322 |
+
// auto scroll
|
323 |
+
useLayoutEffect(() => {
|
324 |
+
setTimeout(() => {
|
325 |
+
const dom = latestMessageRef.current;
|
326 |
+
const inputDom = inputRef.current;
|
327 |
+
|
328 |
+
// only scroll when input overlaped message body
|
329 |
+
let shouldScroll = true;
|
330 |
+
if (dom && inputDom) {
|
331 |
+
const domRect = dom.getBoundingClientRect();
|
332 |
+
const inputRect = inputDom.getBoundingClientRect();
|
333 |
+
shouldScroll = domRect.top > inputRect.top;
|
334 |
+
}
|
335 |
+
|
336 |
+
if (dom && autoScroll && shouldScroll) {
|
337 |
+
dom.scrollIntoView({
|
338 |
+
block: "end",
|
339 |
+
});
|
340 |
+
}
|
341 |
+
}, 500);
|
342 |
+
});
|
343 |
+
|
344 |
+
return (
|
345 |
+
<div className={styles.chat} key={session.id}>
|
346 |
+
<div className={styles["window-header"]}>
|
347 |
+
<div
|
348 |
+
className={styles["window-header-title"]}
|
349 |
+
onClick={props?.showSideBar}
|
350 |
+
>
|
351 |
+
<div
|
352 |
+
className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`}
|
353 |
+
onClick={() => {
|
354 |
+
const newTopic = prompt(Locale.Chat.Rename, session.topic);
|
355 |
+
if (newTopic && newTopic !== session.topic) {
|
356 |
+
chatStore.updateCurrentSession(
|
357 |
+
(session) => (session.topic = newTopic!),
|
358 |
+
);
|
359 |
+
}
|
360 |
+
}}
|
361 |
+
>
|
362 |
+
{session.topic}
|
363 |
+
</div>
|
364 |
+
<div className={styles["window-header-sub-title"]}>
|
365 |
+
{Locale.Chat.SubTitle(session.messages.length)}
|
366 |
+
</div>
|
367 |
+
</div>
|
368 |
+
<div className={styles["window-actions"]}>
|
369 |
+
<div className={styles["window-action-button"] + " " + styles.mobile}>
|
370 |
+
<IconButton
|
371 |
+
icon={<MenuIcon />}
|
372 |
+
bordered
|
373 |
+
title={Locale.Chat.Actions.ChatList}
|
374 |
+
onClick={props?.showSideBar}
|
375 |
+
/>
|
376 |
+
</div>
|
377 |
+
<div className={styles["window-action-button"]}>
|
378 |
+
<IconButton
|
379 |
+
icon={<BrainIcon />}
|
380 |
+
bordered
|
381 |
+
title={Locale.Chat.Actions.CompressedHistory}
|
382 |
+
onClick={() => {
|
383 |
+
showMemoryPrompt(session);
|
384 |
+
}}
|
385 |
+
/>
|
386 |
+
</div>
|
387 |
+
<div className={styles["window-action-button"]}>
|
388 |
+
<IconButton
|
389 |
+
icon={<ExportIcon />}
|
390 |
+
bordered
|
391 |
+
title={Locale.Chat.Actions.Export}
|
392 |
+
onClick={() => {
|
393 |
+
exportMessages(session.messages, session.topic);
|
394 |
+
}}
|
395 |
+
/>
|
396 |
+
</div>
|
397 |
+
</div>
|
398 |
+
</div>
|
399 |
+
|
400 |
+
<div className={styles["chat-body"]}>
|
401 |
+
{messages.map((message, i) => {
|
402 |
+
const isUser = message.role === "user";
|
403 |
+
|
404 |
+
return (
|
405 |
+
<div
|
406 |
+
key={i}
|
407 |
+
className={
|
408 |
+
isUser ? styles["chat-message-user"] : styles["chat-message"]
|
409 |
+
}
|
410 |
+
>
|
411 |
+
<div className={styles["chat-message-container"]}>
|
412 |
+
<div className={styles["chat-message-avatar"]}>
|
413 |
+
<Avatar role={message.role} />
|
414 |
+
</div>
|
415 |
+
{(message.preview || message.streaming) && (
|
416 |
+
<div className={styles["chat-message-status"]}>
|
417 |
+
{Locale.Chat.Typing}
|
418 |
+
</div>
|
419 |
+
)}
|
420 |
+
<div className={styles["chat-message-item"]}>
|
421 |
+
{!isUser &&
|
422 |
+
!(message.preview || message.content.length === 0) && (
|
423 |
+
<div className={styles["chat-message-top-actions"]}>
|
424 |
+
{message.streaming ? (
|
425 |
+
<div
|
426 |
+
className={styles["chat-message-top-action"]}
|
427 |
+
onClick={() => onUserStop(i)}
|
428 |
+
>
|
429 |
+
{Locale.Chat.Actions.Stop}
|
430 |
+
</div>
|
431 |
+
) : (
|
432 |
+
<div
|
433 |
+
className={styles["chat-message-top-action"]}
|
434 |
+
onClick={() => onResend(i)}
|
435 |
+
>
|
436 |
+
{Locale.Chat.Actions.Retry}
|
437 |
+
</div>
|
438 |
+
)}
|
439 |
+
|
440 |
+
<div
|
441 |
+
className={styles["chat-message-top-action"]}
|
442 |
+
onClick={() => copyToClipboard(message.content)}
|
443 |
+
>
|
444 |
+
{Locale.Chat.Actions.Copy}
|
445 |
+
</div>
|
446 |
+
</div>
|
447 |
+
)}
|
448 |
+
{(message.preview || message.content.length === 0) &&
|
449 |
+
!isUser ? (
|
450 |
+
<LoadingIcon />
|
451 |
+
) : (
|
452 |
+
<div
|
453 |
+
className="markdown-body"
|
454 |
+
style={{ fontSize: `${fontSize}px` }}
|
455 |
+
onContextMenu={(e) => onRightClick(e, message)}
|
456 |
+
onDoubleClickCapture={() => setUserInput(message.content)}
|
457 |
+
>
|
458 |
+
<Markdown content={message.content} />
|
459 |
+
</div>
|
460 |
+
)}
|
461 |
+
</div>
|
462 |
+
{!isUser && !message.preview && (
|
463 |
+
<div className={styles["chat-message-actions"]}>
|
464 |
+
<div className={styles["chat-message-action-date"]}>
|
465 |
+
{message.date.toLocaleString()}
|
466 |
+
</div>
|
467 |
+
</div>
|
468 |
+
)}
|
469 |
+
</div>
|
470 |
+
</div>
|
471 |
+
);
|
472 |
+
})}
|
473 |
+
<div ref={latestMessageRef} style={{ opacity: 0, height: "4em" }}>
|
474 |
+
-
|
475 |
+
</div>
|
476 |
+
</div>
|
477 |
+
|
478 |
+
<div className={styles["chat-input-panel"]}>
|
479 |
+
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
|
480 |
+
<div className={styles["chat-input-panel-inner"]}>
|
481 |
+
<textarea
|
482 |
+
ref={inputRef}
|
483 |
+
className={styles["chat-input"]}
|
484 |
+
placeholder={Locale.Chat.Input(submitKey)}
|
485 |
+
rows={4}
|
486 |
+
onInput={(e) => onInput(e.currentTarget.value)}
|
487 |
+
value={userInput}
|
488 |
+
onKeyDown={(e) => onInputKeyDown(e as any)}
|
489 |
+
onFocus={() => setAutoScroll(true)}
|
490 |
+
onBlur={() => {
|
491 |
+
setAutoScroll(false);
|
492 |
+
setTimeout(() => setPromptHints([]), 500);
|
493 |
+
}}
|
494 |
+
autoFocus={!props?.sideBarShowing}
|
495 |
+
/>
|
496 |
+
<IconButton
|
497 |
+
icon={<SendWhiteIcon />}
|
498 |
+
text={Locale.Chat.Send}
|
499 |
+
className={styles["chat-input-send"] + " no-dark"}
|
500 |
+
onClick={onUserSubmit}
|
501 |
+
/>
|
502 |
+
</div>
|
503 |
+
</div>
|
504 |
+
</div>
|
505 |
+
);
|
506 |
+
}
|
507 |
+
|
508 |
+
function useSwitchTheme() {
|
509 |
+
const config = useChatStore((state) => state.config);
|
510 |
+
|
511 |
+
useEffect(() => {
|
512 |
+
document.body.classList.remove("light");
|
513 |
+
document.body.classList.remove("dark");
|
514 |
+
|
515 |
+
if (config.theme === "dark") {
|
516 |
+
document.body.classList.add("dark");
|
517 |
+
} else if (config.theme === "light") {
|
518 |
+
document.body.classList.add("light");
|
519 |
+
}
|
520 |
+
|
521 |
+
const themeColor = getComputedStyle(document.body)
|
522 |
+
.getPropertyValue("--theme-color")
|
523 |
+
.trim();
|
524 |
+
const metaDescription = document.querySelector('meta[name="theme-color"]');
|
525 |
+
metaDescription?.setAttribute("content", themeColor);
|
526 |
+
}, [config.theme]);
|
527 |
+
}
|
528 |
+
|
529 |
+
function exportMessages(messages: Message[], topic: string) {
|
530 |
+
const mdText =
|
531 |
+
`# ${topic}\n\n` +
|
532 |
+
messages
|
533 |
+
.map((m) => {
|
534 |
+
return m.role === "user" ? `## ${m.content}` : m.content.trim();
|
535 |
+
})
|
536 |
+
.join("\n\n");
|
537 |
+
const filename = `${topic}.md`;
|
538 |
+
|
539 |
+
showModal({
|
540 |
+
title: Locale.Export.Title,
|
541 |
+
children: (
|
542 |
+
<div className="markdown-body">
|
543 |
+
<pre className={styles["export-content"]}>{mdText}</pre>
|
544 |
+
</div>
|
545 |
+
),
|
546 |
+
actions: [
|
547 |
+
<IconButton
|
548 |
+
key="copy"
|
549 |
+
icon={<CopyIcon />}
|
550 |
+
bordered
|
551 |
+
text={Locale.Export.Copy}
|
552 |
+
onClick={() => copyToClipboard(mdText)}
|
553 |
+
/>,
|
554 |
+
<IconButton
|
555 |
+
key="download"
|
556 |
+
icon={<DownloadIcon />}
|
557 |
+
bordered
|
558 |
+
text={Locale.Export.Download}
|
559 |
+
onClick={() => downloadAs(mdText, filename)}
|
560 |
+
/>,
|
561 |
+
],
|
562 |
+
});
|
563 |
+
}
|
564 |
+
|
565 |
+
function showMemoryPrompt(session: ChatSession) {
|
566 |
+
showModal({
|
567 |
+
title: `${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`,
|
568 |
+
children: (
|
569 |
+
<div className="markdown-body">
|
570 |
+
<pre className={styles["export-content"]}>
|
571 |
+
{session.memoryPrompt || Locale.Memory.EmptyContent}
|
572 |
+
</pre>
|
573 |
+
</div>
|
574 |
+
),
|
575 |
+
actions: [
|
576 |
+
<IconButton
|
577 |
+
key="copy"
|
578 |
+
icon={<CopyIcon />}
|
579 |
+
bordered
|
580 |
+
text={Locale.Memory.Copy}
|
581 |
+
onClick={() => copyToClipboard(session.memoryPrompt)}
|
582 |
+
/>,
|
583 |
+
],
|
584 |
+
});
|
585 |
+
}
|
586 |
+
|
587 |
+
const useHasHydrated = () => {
|
588 |
+
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
|
589 |
+
|
590 |
+
useEffect(() => {
|
591 |
+
setHasHydrated(true);
|
592 |
+
}, []);
|
593 |
+
|
594 |
+
return hasHydrated;
|
595 |
+
};
|
596 |
+
|
597 |
+
export function Home() {
|
598 |
+
const [createNewSession, currentIndex, removeSession] = useChatStore(
|
599 |
+
(state) => [
|
600 |
+
state.newSession,
|
601 |
+
state.currentSessionIndex,
|
602 |
+
state.removeSession,
|
603 |
+
],
|
604 |
+
);
|
605 |
+
const loading = !useHasHydrated();
|
606 |
+
const [showSideBar, setShowSideBar] = useState(true);
|
607 |
+
|
608 |
+
// setting
|
609 |
+
const [openSettings, setOpenSettings] = useState(false);
|
610 |
+
const config = useChatStore((state) => state.config);
|
611 |
+
|
612 |
+
useSwitchTheme();
|
613 |
+
|
614 |
+
if (loading) {
|
615 |
+
return <Loading />;
|
616 |
+
}
|
617 |
+
|
618 |
+
return (
|
619 |
+
<div
|
620 |
+
className={`${
|
621 |
+
config.tightBorder && !isMobileScreen()
|
622 |
+
? styles["tight-container"]
|
623 |
+
: styles.container
|
624 |
+
}`}
|
625 |
+
>
|
626 |
+
<div
|
627 |
+
className={styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`}
|
628 |
+
>
|
629 |
+
<div className={styles["sidebar-header"]}>
|
630 |
+
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
631 |
+
<div className={styles["sidebar-sub-title"]}>
|
632 |
+
Build your own AI assistant.
|
633 |
+
</div>
|
634 |
+
<div className={styles["sidebar-logo"]}>
|
635 |
+
<ChatGptIcon />
|
636 |
+
</div>
|
637 |
+
</div>
|
638 |
+
|
639 |
+
<div
|
640 |
+
className={styles["sidebar-body"]}
|
641 |
+
onClick={() => {
|
642 |
+
setOpenSettings(false);
|
643 |
+
setShowSideBar(false);
|
644 |
+
}}
|
645 |
+
>
|
646 |
+
<ChatList />
|
647 |
+
</div>
|
648 |
+
|
649 |
+
<div className={styles["sidebar-tail"]}>
|
650 |
+
<div className={styles["sidebar-actions"]}>
|
651 |
+
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
652 |
+
<IconButton
|
653 |
+
icon={<CloseIcon />}
|
654 |
+
onClick={() => {
|
655 |
+
if (confirm(Locale.Home.DeleteChat)) {
|
656 |
+
removeSession(currentIndex);
|
657 |
+
}
|
658 |
+
}}
|
659 |
+
/>
|
660 |
+
</div>
|
661 |
+
<div className={styles["sidebar-action"]}>
|
662 |
+
<IconButton
|
663 |
+
icon={<SettingsIcon />}
|
664 |
+
onClick={() => {
|
665 |
+
setOpenSettings(true);
|
666 |
+
setShowSideBar(false);
|
667 |
+
}}
|
668 |
+
/>
|
669 |
+
</div>
|
670 |
+
<div className={styles["sidebar-action"]}>
|
671 |
+
<a href={REPO_URL} target="_blank">
|
672 |
+
<IconButton icon={<GithubIcon />} />
|
673 |
+
</a>
|
674 |
+
</div>
|
675 |
+
</div>
|
676 |
+
<div>
|
677 |
+
<IconButton
|
678 |
+
icon={<AddIcon />}
|
679 |
+
text={Locale.Home.NewChat}
|
680 |
+
onClick={() => {
|
681 |
+
createNewSession();
|
682 |
+
setShowSideBar(false);
|
683 |
+
}}
|
684 |
+
/>
|
685 |
+
</div>
|
686 |
+
</div>
|
687 |
+
</div>
|
688 |
+
|
689 |
+
<div className={styles["window-content"]}>
|
690 |
+
{openSettings ? (
|
691 |
+
<Settings
|
692 |
+
closeSettings={() => {
|
693 |
+
setOpenSettings(false);
|
694 |
+
setShowSideBar(true);
|
695 |
+
}}
|
696 |
+
/>
|
697 |
+
) : (
|
698 |
+
<Chat
|
699 |
+
key="chat"
|
700 |
+
showSideBar={() => setShowSideBar(true)}
|
701 |
+
sideBarShowing={showSideBar}
|
702 |
+
/>
|
703 |
+
)}
|
704 |
+
</div>
|
705 |
+
</div>
|
706 |
+
);
|
707 |
+
}
|
app/components/markdown.tsx
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ReactMarkdown from "react-markdown";
|
2 |
+
import "katex/dist/katex.min.css";
|
3 |
+
import RemarkMath from "remark-math";
|
4 |
+
import RemarkBreaks from "remark-breaks";
|
5 |
+
import RehypeKatex from "rehype-katex";
|
6 |
+
import RemarkGfm from "remark-gfm";
|
7 |
+
import RehypePrsim from "rehype-prism-plus";
|
8 |
+
import { useRef } from "react";
|
9 |
+
import { copyToClipboard } from "../utils";
|
10 |
+
|
11 |
+
export function PreCode(props: { children: any }) {
|
12 |
+
const ref = useRef<HTMLPreElement>(null);
|
13 |
+
|
14 |
+
return (
|
15 |
+
<pre ref={ref}>
|
16 |
+
<span
|
17 |
+
className="copy-code-button"
|
18 |
+
onClick={() => {
|
19 |
+
if (ref.current) {
|
20 |
+
const code = ref.current.innerText;
|
21 |
+
copyToClipboard(code);
|
22 |
+
}
|
23 |
+
}}
|
24 |
+
></span>
|
25 |
+
{props.children}
|
26 |
+
</pre>
|
27 |
+
);
|
28 |
+
}
|
29 |
+
|
30 |
+
export function Markdown(props: { content: string }) {
|
31 |
+
return (
|
32 |
+
<ReactMarkdown
|
33 |
+
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
34 |
+
rehypePlugins={[RehypeKatex, [RehypePrsim, { ignoreMissing: true }]]}
|
35 |
+
components={{
|
36 |
+
pre: PreCode,
|
37 |
+
}}
|
38 |
+
>
|
39 |
+
{props.content}
|
40 |
+
</ReactMarkdown>
|
41 |
+
);
|
42 |
+
}
|
app/components/settings.module.scss
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "./window.scss";
|
2 |
+
|
3 |
+
.settings {
|
4 |
+
padding: 20px;
|
5 |
+
overflow: auto;
|
6 |
+
}
|
7 |
+
|
8 |
+
.settings-title {
|
9 |
+
font-size: 14px;
|
10 |
+
font-weight: bolder;
|
11 |
+
}
|
12 |
+
|
13 |
+
.settings-sub-title {
|
14 |
+
font-size: 12px;
|
15 |
+
font-weight: normal;
|
16 |
+
}
|
17 |
+
|
18 |
+
.avatar {
|
19 |
+
cursor: pointer;
|
20 |
+
}
|
app/components/settings.tsx
ADDED
@@ -0,0 +1,486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useEffect, useRef, useMemo } from "react";
|
2 |
+
|
3 |
+
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
|
4 |
+
|
5 |
+
import styles from "./settings.module.scss";
|
6 |
+
|
7 |
+
import ResetIcon from "../icons/reload.svg";
|
8 |
+
import CloseIcon from "../icons/close.svg";
|
9 |
+
import ClearIcon from "../icons/clear.svg";
|
10 |
+
import EditIcon from "../icons/edit.svg";
|
11 |
+
|
12 |
+
import { List, ListItem, Popover, showToast } from "./ui-lib";
|
13 |
+
|
14 |
+
import { IconButton } from "./button";
|
15 |
+
import {
|
16 |
+
SubmitKey,
|
17 |
+
useChatStore,
|
18 |
+
Theme,
|
19 |
+
ALL_MODELS,
|
20 |
+
useUpdateStore,
|
21 |
+
useAccessStore,
|
22 |
+
} from "../store";
|
23 |
+
import { Avatar, PromptHints } from "./home";
|
24 |
+
|
25 |
+
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
26 |
+
import { getCurrentVersion } from "../utils";
|
27 |
+
import Link from "next/link";
|
28 |
+
import { UPDATE_URL } from "../constant";
|
29 |
+
import { SearchService, usePromptStore } from "../store/prompt";
|
30 |
+
import { requestUsage } from "../requests";
|
31 |
+
|
32 |
+
function SettingItem(props: {
|
33 |
+
title: string;
|
34 |
+
subTitle?: string;
|
35 |
+
children: JSX.Element;
|
36 |
+
}) {
|
37 |
+
return (
|
38 |
+
<ListItem>
|
39 |
+
<div className={styles["settings-title"]}>
|
40 |
+
<div>{props.title}</div>
|
41 |
+
{props.subTitle && (
|
42 |
+
<div className={styles["settings-sub-title"]}>{props.subTitle}</div>
|
43 |
+
)}
|
44 |
+
</div>
|
45 |
+
{props.children}
|
46 |
+
</ListItem>
|
47 |
+
);
|
48 |
+
}
|
49 |
+
|
50 |
+
export function Settings(props: { closeSettings: () => void }) {
|
51 |
+
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
52 |
+
const [config, updateConfig, resetConfig, clearAllData] = useChatStore(
|
53 |
+
(state) => [
|
54 |
+
state.config,
|
55 |
+
state.updateConfig,
|
56 |
+
state.resetConfig,
|
57 |
+
state.clearAllData,
|
58 |
+
],
|
59 |
+
);
|
60 |
+
|
61 |
+
const updateStore = useUpdateStore();
|
62 |
+
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
63 |
+
const currentId = getCurrentVersion();
|
64 |
+
const remoteId = updateStore.remoteId;
|
65 |
+
const hasNewVersion = currentId !== remoteId;
|
66 |
+
|
67 |
+
function checkUpdate(force = false) {
|
68 |
+
setCheckingUpdate(true);
|
69 |
+
updateStore.getLatestCommitId(force).then(() => {
|
70 |
+
setCheckingUpdate(false);
|
71 |
+
});
|
72 |
+
}
|
73 |
+
|
74 |
+
const [usage, setUsage] = useState<{
|
75 |
+
granted?: number;
|
76 |
+
used?: number;
|
77 |
+
}>();
|
78 |
+
const [loadingUsage, setLoadingUsage] = useState(false);
|
79 |
+
function checkUsage() {
|
80 |
+
setLoadingUsage(true);
|
81 |
+
requestUsage()
|
82 |
+
.then((res) =>
|
83 |
+
setUsage({
|
84 |
+
granted: res?.total_granted,
|
85 |
+
used: res?.total_used,
|
86 |
+
}),
|
87 |
+
)
|
88 |
+
.finally(() => {
|
89 |
+
setLoadingUsage(false);
|
90 |
+
});
|
91 |
+
}
|
92 |
+
|
93 |
+
useEffect(() => {
|
94 |
+
checkUpdate();
|
95 |
+
checkUsage();
|
96 |
+
}, []);
|
97 |
+
|
98 |
+
const accessStore = useAccessStore();
|
99 |
+
const enabledAccessControl = useMemo(
|
100 |
+
() => accessStore.enabledAccessControl(),
|
101 |
+
[],
|
102 |
+
);
|
103 |
+
|
104 |
+
const promptStore = usePromptStore();
|
105 |
+
const builtinCount = SearchService.count.builtin;
|
106 |
+
const customCount = promptStore.prompts.size ?? 0;
|
107 |
+
|
108 |
+
return (
|
109 |
+
<>
|
110 |
+
<div className={styles["window-header"]}>
|
111 |
+
<div className={styles["window-header-title"]}>
|
112 |
+
<div className={styles["window-header-main-title"]}>
|
113 |
+
{Locale.Settings.Title}
|
114 |
+
</div>
|
115 |
+
<div className={styles["window-header-sub-title"]}>
|
116 |
+
{Locale.Settings.SubTitle}
|
117 |
+
</div>
|
118 |
+
</div>
|
119 |
+
<div className={styles["window-actions"]}>
|
120 |
+
<div className={styles["window-action-button"]}>
|
121 |
+
<IconButton
|
122 |
+
icon={<ClearIcon />}
|
123 |
+
onClick={clearAllData}
|
124 |
+
bordered
|
125 |
+
title={Locale.Settings.Actions.ClearAll}
|
126 |
+
/>
|
127 |
+
</div>
|
128 |
+
<div className={styles["window-action-button"]}>
|
129 |
+
<IconButton
|
130 |
+
icon={<ResetIcon />}
|
131 |
+
onClick={resetConfig}
|
132 |
+
bordered
|
133 |
+
title={Locale.Settings.Actions.ResetAll}
|
134 |
+
/>
|
135 |
+
</div>
|
136 |
+
<div className={styles["window-action-button"]}>
|
137 |
+
<IconButton
|
138 |
+
icon={<CloseIcon />}
|
139 |
+
onClick={props.closeSettings}
|
140 |
+
bordered
|
141 |
+
title={Locale.Settings.Actions.Close}
|
142 |
+
/>
|
143 |
+
</div>
|
144 |
+
</div>
|
145 |
+
</div>
|
146 |
+
<div className={styles["settings"]}>
|
147 |
+
<List>
|
148 |
+
<SettingItem title={Locale.Settings.Avatar}>
|
149 |
+
<Popover
|
150 |
+
onClose={() => setShowEmojiPicker(false)}
|
151 |
+
content={
|
152 |
+
<EmojiPicker
|
153 |
+
lazyLoadEmojis
|
154 |
+
theme={EmojiTheme.AUTO}
|
155 |
+
onEmojiClick={(e) => {
|
156 |
+
updateConfig((config) => (config.avatar = e.unified));
|
157 |
+
setShowEmojiPicker(false);
|
158 |
+
}}
|
159 |
+
/>
|
160 |
+
}
|
161 |
+
open={showEmojiPicker}
|
162 |
+
>
|
163 |
+
<div
|
164 |
+
className={styles.avatar}
|
165 |
+
onClick={() => setShowEmojiPicker(true)}
|
166 |
+
>
|
167 |
+
<Avatar role="user" />
|
168 |
+
</div>
|
169 |
+
</Popover>
|
170 |
+
</SettingItem>
|
171 |
+
|
172 |
+
<SettingItem
|
173 |
+
title={Locale.Settings.Update.Version(currentId)}
|
174 |
+
subTitle={
|
175 |
+
checkingUpdate
|
176 |
+
? Locale.Settings.Update.IsChecking
|
177 |
+
: hasNewVersion
|
178 |
+
? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
|
179 |
+
: Locale.Settings.Update.IsLatest
|
180 |
+
}
|
181 |
+
>
|
182 |
+
{checkingUpdate ? (
|
183 |
+
<div />
|
184 |
+
) : hasNewVersion ? (
|
185 |
+
<Link href={UPDATE_URL} target="_blank" className="link">
|
186 |
+
{Locale.Settings.Update.GoToUpdate}
|
187 |
+
</Link>
|
188 |
+
) : (
|
189 |
+
<IconButton
|
190 |
+
icon={<ResetIcon></ResetIcon>}
|
191 |
+
text={Locale.Settings.Update.CheckUpdate}
|
192 |
+
onClick={() => checkUpdate(true)}
|
193 |
+
/>
|
194 |
+
)}
|
195 |
+
</SettingItem>
|
196 |
+
|
197 |
+
<SettingItem title={Locale.Settings.SendKey}>
|
198 |
+
<select
|
199 |
+
value={config.submitKey}
|
200 |
+
onChange={(e) => {
|
201 |
+
updateConfig(
|
202 |
+
(config) =>
|
203 |
+
(config.submitKey = e.target.value as any as SubmitKey),
|
204 |
+
);
|
205 |
+
}}
|
206 |
+
>
|
207 |
+
{Object.values(SubmitKey).map((v) => (
|
208 |
+
<option value={v} key={v}>
|
209 |
+
{v}
|
210 |
+
</option>
|
211 |
+
))}
|
212 |
+
</select>
|
213 |
+
</SettingItem>
|
214 |
+
|
215 |
+
<ListItem>
|
216 |
+
<div className={styles["settings-title"]}>
|
217 |
+
{Locale.Settings.Theme}
|
218 |
+
</div>
|
219 |
+
<select
|
220 |
+
value={config.theme}
|
221 |
+
onChange={(e) => {
|
222 |
+
updateConfig(
|
223 |
+
(config) => (config.theme = e.target.value as any as Theme),
|
224 |
+
);
|
225 |
+
}}
|
226 |
+
>
|
227 |
+
{Object.values(Theme).map((v) => (
|
228 |
+
<option value={v} key={v}>
|
229 |
+
{v}
|
230 |
+
</option>
|
231 |
+
))}
|
232 |
+
</select>
|
233 |
+
</ListItem>
|
234 |
+
|
235 |
+
<SettingItem title={Locale.Settings.Lang.Name}>
|
236 |
+
<select
|
237 |
+
value={getLang()}
|
238 |
+
onChange={(e) => {
|
239 |
+
changeLang(e.target.value as any);
|
240 |
+
}}
|
241 |
+
>
|
242 |
+
{AllLangs.map((lang) => (
|
243 |
+
<option value={lang} key={lang}>
|
244 |
+
{Locale.Settings.Lang.Options[lang]}
|
245 |
+
</option>
|
246 |
+
))}
|
247 |
+
</select>
|
248 |
+
</SettingItem>
|
249 |
+
|
250 |
+
<SettingItem
|
251 |
+
title={Locale.Settings.FontSize.Title}
|
252 |
+
subTitle={Locale.Settings.FontSize.SubTitle}
|
253 |
+
>
|
254 |
+
<input
|
255 |
+
type="range"
|
256 |
+
title={`${config.fontSize ?? 14}px`}
|
257 |
+
value={config.fontSize}
|
258 |
+
min="12"
|
259 |
+
max="18"
|
260 |
+
step="1"
|
261 |
+
onChange={(e) =>
|
262 |
+
updateConfig(
|
263 |
+
(config) =>
|
264 |
+
(config.fontSize = Number.parseInt(e.currentTarget.value)),
|
265 |
+
)
|
266 |
+
}
|
267 |
+
></input>
|
268 |
+
</SettingItem>
|
269 |
+
|
270 |
+
<SettingItem title={Locale.Settings.TightBorder}>
|
271 |
+
<input
|
272 |
+
type="checkbox"
|
273 |
+
checked={config.tightBorder}
|
274 |
+
onChange={(e) =>
|
275 |
+
updateConfig(
|
276 |
+
(config) => (config.tightBorder = e.currentTarget.checked),
|
277 |
+
)
|
278 |
+
}
|
279 |
+
></input>
|
280 |
+
</SettingItem>
|
281 |
+
</List>
|
282 |
+
<List>
|
283 |
+
<SettingItem
|
284 |
+
title={Locale.Settings.Prompt.Disable.Title}
|
285 |
+
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
|
286 |
+
>
|
287 |
+
<input
|
288 |
+
type="checkbox"
|
289 |
+
checked={config.disablePromptHint}
|
290 |
+
onChange={(e) =>
|
291 |
+
updateConfig(
|
292 |
+
(config) =>
|
293 |
+
(config.disablePromptHint = e.currentTarget.checked),
|
294 |
+
)
|
295 |
+
}
|
296 |
+
></input>
|
297 |
+
</SettingItem>
|
298 |
+
|
299 |
+
<SettingItem
|
300 |
+
title={Locale.Settings.Prompt.List}
|
301 |
+
subTitle={Locale.Settings.Prompt.ListCount(
|
302 |
+
builtinCount,
|
303 |
+
customCount,
|
304 |
+
)}
|
305 |
+
>
|
306 |
+
<IconButton
|
307 |
+
icon={<EditIcon />}
|
308 |
+
text={Locale.Settings.Prompt.Edit}
|
309 |
+
onClick={() => showToast(Locale.WIP)}
|
310 |
+
/>
|
311 |
+
</SettingItem>
|
312 |
+
</List>
|
313 |
+
<List>
|
314 |
+
{enabledAccessControl ? (
|
315 |
+
<SettingItem
|
316 |
+
title={Locale.Settings.AccessCode.Title}
|
317 |
+
subTitle={Locale.Settings.AccessCode.SubTitle}
|
318 |
+
>
|
319 |
+
<input
|
320 |
+
value={accessStore.accessCode}
|
321 |
+
type="text"
|
322 |
+
placeholder={Locale.Settings.AccessCode.Placeholder}
|
323 |
+
onChange={(e) => {
|
324 |
+
accessStore.updateCode(e.currentTarget.value);
|
325 |
+
}}
|
326 |
+
></input>
|
327 |
+
</SettingItem>
|
328 |
+
) : (
|
329 |
+
<></>
|
330 |
+
)}
|
331 |
+
|
332 |
+
<SettingItem
|
333 |
+
title={Locale.Settings.Token.Title}
|
334 |
+
subTitle={Locale.Settings.Token.SubTitle}
|
335 |
+
>
|
336 |
+
<input
|
337 |
+
value={accessStore.token}
|
338 |
+
type="text"
|
339 |
+
placeholder={Locale.Settings.Token.Placeholder}
|
340 |
+
onChange={(e) => {
|
341 |
+
accessStore.updateToken(e.currentTarget.value);
|
342 |
+
}}
|
343 |
+
></input>
|
344 |
+
</SettingItem>
|
345 |
+
|
346 |
+
<SettingItem
|
347 |
+
title={Locale.Settings.Usage.Title}
|
348 |
+
subTitle={
|
349 |
+
loadingUsage
|
350 |
+
? Locale.Settings.Usage.IsChecking
|
351 |
+
: Locale.Settings.Usage.SubTitle(
|
352 |
+
usage?.granted ?? "[?]",
|
353 |
+
usage?.used ?? "[?]",
|
354 |
+
)
|
355 |
+
}
|
356 |
+
>
|
357 |
+
{loadingUsage ? (
|
358 |
+
<div />
|
359 |
+
) : (
|
360 |
+
<IconButton
|
361 |
+
icon={<ResetIcon></ResetIcon>}
|
362 |
+
text={Locale.Settings.Usage.Check}
|
363 |
+
onClick={checkUsage}
|
364 |
+
/>
|
365 |
+
)}
|
366 |
+
</SettingItem>
|
367 |
+
|
368 |
+
<SettingItem
|
369 |
+
title={Locale.Settings.HistoryCount.Title}
|
370 |
+
subTitle={Locale.Settings.HistoryCount.SubTitle}
|
371 |
+
>
|
372 |
+
<input
|
373 |
+
type="range"
|
374 |
+
title={config.historyMessageCount.toString()}
|
375 |
+
value={config.historyMessageCount}
|
376 |
+
min="0"
|
377 |
+
max="25"
|
378 |
+
step="2"
|
379 |
+
onChange={(e) =>
|
380 |
+
updateConfig(
|
381 |
+
(config) =>
|
382 |
+
(config.historyMessageCount = e.target.valueAsNumber),
|
383 |
+
)
|
384 |
+
}
|
385 |
+
></input>
|
386 |
+
</SettingItem>
|
387 |
+
|
388 |
+
<SettingItem
|
389 |
+
title={Locale.Settings.CompressThreshold.Title}
|
390 |
+
subTitle={Locale.Settings.CompressThreshold.SubTitle}
|
391 |
+
>
|
392 |
+
<input
|
393 |
+
type="number"
|
394 |
+
min={500}
|
395 |
+
max={4000}
|
396 |
+
value={config.compressMessageLengthThreshold}
|
397 |
+
onChange={(e) =>
|
398 |
+
updateConfig(
|
399 |
+
(config) =>
|
400 |
+
(config.compressMessageLengthThreshold =
|
401 |
+
e.currentTarget.valueAsNumber),
|
402 |
+
)
|
403 |
+
}
|
404 |
+
></input>
|
405 |
+
</SettingItem>
|
406 |
+
</List>
|
407 |
+
|
408 |
+
<List>
|
409 |
+
<SettingItem title={Locale.Settings.Model}>
|
410 |
+
<select
|
411 |
+
value={config.modelConfig.model}
|
412 |
+
onChange={(e) => {
|
413 |
+
updateConfig(
|
414 |
+
(config) =>
|
415 |
+
(config.modelConfig.model = e.currentTarget.value),
|
416 |
+
);
|
417 |
+
}}
|
418 |
+
>
|
419 |
+
{ALL_MODELS.map((v) => (
|
420 |
+
<option value={v.name} key={v.name} disabled={!v.available}>
|
421 |
+
{v.name}
|
422 |
+
</option>
|
423 |
+
))}
|
424 |
+
</select>
|
425 |
+
</SettingItem>
|
426 |
+
<SettingItem
|
427 |
+
title={Locale.Settings.Temperature.Title}
|
428 |
+
subTitle={Locale.Settings.Temperature.SubTitle}
|
429 |
+
>
|
430 |
+
<input
|
431 |
+
type="range"
|
432 |
+
value={config.modelConfig.temperature.toFixed(1)}
|
433 |
+
min="0"
|
434 |
+
max="2"
|
435 |
+
step="0.1"
|
436 |
+
onChange={(e) => {
|
437 |
+
updateConfig(
|
438 |
+
(config) =>
|
439 |
+
(config.modelConfig.temperature =
|
440 |
+
e.currentTarget.valueAsNumber),
|
441 |
+
);
|
442 |
+
}}
|
443 |
+
></input>
|
444 |
+
</SettingItem>
|
445 |
+
<SettingItem
|
446 |
+
title={Locale.Settings.MaxTokens.Title}
|
447 |
+
subTitle={Locale.Settings.MaxTokens.SubTitle}
|
448 |
+
>
|
449 |
+
<input
|
450 |
+
type="number"
|
451 |
+
min={100}
|
452 |
+
max={4096}
|
453 |
+
value={config.modelConfig.max_tokens}
|
454 |
+
onChange={(e) =>
|
455 |
+
updateConfig(
|
456 |
+
(config) =>
|
457 |
+
(config.modelConfig.max_tokens =
|
458 |
+
e.currentTarget.valueAsNumber),
|
459 |
+
)
|
460 |
+
}
|
461 |
+
></input>
|
462 |
+
</SettingItem>
|
463 |
+
<SettingItem
|
464 |
+
title={Locale.Settings.PresencePenlty.Title}
|
465 |
+
subTitle={Locale.Settings.PresencePenlty.SubTitle}
|
466 |
+
>
|
467 |
+
<input
|
468 |
+
type="range"
|
469 |
+
value={config.modelConfig.presence_penalty.toFixed(1)}
|
470 |
+
min="-2"
|
471 |
+
max="2"
|
472 |
+
step="0.5"
|
473 |
+
onChange={(e) => {
|
474 |
+
updateConfig(
|
475 |
+
(config) =>
|
476 |
+
(config.modelConfig.presence_penalty =
|
477 |
+
e.currentTarget.valueAsNumber),
|
478 |
+
);
|
479 |
+
}}
|
480 |
+
></input>
|
481 |
+
</SettingItem>
|
482 |
+
</List>
|
483 |
+
</div>
|
484 |
+
</>
|
485 |
+
);
|
486 |
+
}
|
app/components/ui-lib.module.scss
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.card {
|
2 |
+
background-color: var(--white);
|
3 |
+
border-radius: 10px;
|
4 |
+
box-shadow: var(--card-shadow);
|
5 |
+
padding: 10px;
|
6 |
+
}
|
7 |
+
|
8 |
+
.popover {
|
9 |
+
position: relative;
|
10 |
+
}
|
11 |
+
|
12 |
+
.popover-content {
|
13 |
+
position: absolute;
|
14 |
+
animation: slide-in 0.3s ease;
|
15 |
+
right: 0;
|
16 |
+
top: calc(100% + 10px);
|
17 |
+
}
|
18 |
+
|
19 |
+
.popover-mask {
|
20 |
+
position: fixed;
|
21 |
+
top: 0;
|
22 |
+
left: 0;
|
23 |
+
width: 100vw;
|
24 |
+
height: 100vh;
|
25 |
+
}
|
26 |
+
|
27 |
+
@keyframes slide-in {
|
28 |
+
from {
|
29 |
+
transform: translateY(10px);
|
30 |
+
opacity: 0;
|
31 |
+
}
|
32 |
+
|
33 |
+
to {
|
34 |
+
transform: translateY(0);
|
35 |
+
opacity: 1;
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
.list-item {
|
40 |
+
display: flex;
|
41 |
+
justify-content: space-between;
|
42 |
+
align-items: center;
|
43 |
+
min-height: 40px;
|
44 |
+
border-bottom: var(--border-in-light);
|
45 |
+
padding: 10px 20px;
|
46 |
+
animation: slide-in ease 0.6s;
|
47 |
+
}
|
48 |
+
|
49 |
+
.list {
|
50 |
+
border: var(--border-in-light);
|
51 |
+
border-radius: 10px;
|
52 |
+
box-shadow: var(--card-shadow);
|
53 |
+
margin-bottom: 20px;
|
54 |
+
animation: slide-in ease 0.3s;
|
55 |
+
}
|
56 |
+
|
57 |
+
.list .list-item:last-child {
|
58 |
+
border: 0;
|
59 |
+
}
|
60 |
+
|
61 |
+
.modal-container {
|
62 |
+
box-shadow: var(--card-shadow);
|
63 |
+
background-color: var(--white);
|
64 |
+
border-radius: 12px;
|
65 |
+
width: 50vw;
|
66 |
+
animation: slide-in ease 0.3s;
|
67 |
+
|
68 |
+
--modal-padding: 20px;
|
69 |
+
|
70 |
+
.modal-header {
|
71 |
+
padding: var(--modal-padding);
|
72 |
+
display: flex;
|
73 |
+
align-items: center;
|
74 |
+
justify-content: space-between;
|
75 |
+
border-bottom: var(--border-in-light);
|
76 |
+
|
77 |
+
.modal-title {
|
78 |
+
font-weight: bolder;
|
79 |
+
font-size: 16px;
|
80 |
+
}
|
81 |
+
|
82 |
+
.modal-close-btn {
|
83 |
+
cursor: pointer;
|
84 |
+
|
85 |
+
&:hover {
|
86 |
+
filter: brightness(1.2);
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
.modal-content {
|
92 |
+
max-height: 40vh;
|
93 |
+
padding: var(--modal-padding);
|
94 |
+
overflow: auto;
|
95 |
+
}
|
96 |
+
|
97 |
+
.modal-footer {
|
98 |
+
padding: var(--modal-padding);
|
99 |
+
display: flex;
|
100 |
+
justify-content: flex-end;
|
101 |
+
|
102 |
+
.modal-actions {
|
103 |
+
display: flex;
|
104 |
+
align-items: center;
|
105 |
+
|
106 |
+
.modal-action {
|
107 |
+
&:not(:last-child) {
|
108 |
+
margin-right: 20px;
|
109 |
+
}
|
110 |
+
}
|
111 |
+
}
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
.show {
|
116 |
+
opacity: 1;
|
117 |
+
transition: all ease 0.3s;
|
118 |
+
transform: translateY(0);
|
119 |
+
position: fixed;
|
120 |
+
left: 0;
|
121 |
+
bottom: 0;
|
122 |
+
animation: slide-in ease 0.6s;
|
123 |
+
z-index: 99999;
|
124 |
+
}
|
125 |
+
|
126 |
+
.hide {
|
127 |
+
opacity: 0;
|
128 |
+
transition: all ease 0.3s;
|
129 |
+
transform: translateY(20px);
|
130 |
+
}
|
131 |
+
|
132 |
+
.toast-container {
|
133 |
+
position: fixed;
|
134 |
+
bottom: 0;
|
135 |
+
left: 0;
|
136 |
+
width: 100vw;
|
137 |
+
display: flex;
|
138 |
+
justify-content: center;
|
139 |
+
|
140 |
+
.toast-content {
|
141 |
+
font-size: 14px;
|
142 |
+
background-color: var(--white);
|
143 |
+
box-shadow: var(--card-shadow);
|
144 |
+
border: var(--border-in-light);
|
145 |
+
color: var(--black);
|
146 |
+
padding: 10px 30px;
|
147 |
+
border-radius: 50px;
|
148 |
+
margin-bottom: 20px;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
@media only screen and (max-width: 600px) {
|
153 |
+
.modal-container {
|
154 |
+
width: 90vw;
|
155 |
+
|
156 |
+
.modal-content {
|
157 |
+
max-height: 50vh;
|
158 |
+
}
|
159 |
+
}
|
160 |
+
}
|
app/components/ui-lib.tsx
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import styles from "./ui-lib.module.scss";
|
2 |
+
import LoadingIcon from "../icons/three-dots.svg";
|
3 |
+
import CloseIcon from "../icons/close.svg";
|
4 |
+
import { createRoot } from "react-dom/client";
|
5 |
+
|
6 |
+
export function Popover(props: {
|
7 |
+
children: JSX.Element;
|
8 |
+
content: JSX.Element;
|
9 |
+
open?: boolean;
|
10 |
+
onClose?: () => void;
|
11 |
+
}) {
|
12 |
+
return (
|
13 |
+
<div className={styles.popover}>
|
14 |
+
{props.children}
|
15 |
+
{props.open && (
|
16 |
+
<div className={styles["popover-content"]}>
|
17 |
+
<div className={styles["popover-mask"]} onClick={props.onClose}></div>
|
18 |
+
{props.content}
|
19 |
+
</div>
|
20 |
+
)}
|
21 |
+
</div>
|
22 |
+
);
|
23 |
+
}
|
24 |
+
|
25 |
+
export function Card(props: { children: JSX.Element[]; className?: string }) {
|
26 |
+
return (
|
27 |
+
<div className={styles.card + " " + props.className}>{props.children}</div>
|
28 |
+
);
|
29 |
+
}
|
30 |
+
|
31 |
+
export function ListItem(props: { children: JSX.Element[] }) {
|
32 |
+
if (props.children.length > 2) {
|
33 |
+
throw Error("Only Support Two Children");
|
34 |
+
}
|
35 |
+
|
36 |
+
return <div className={styles["list-item"]}>{props.children}</div>;
|
37 |
+
}
|
38 |
+
|
39 |
+
export function List(props: { children: JSX.Element[] | JSX.Element }) {
|
40 |
+
return <div className={styles.list}>{props.children}</div>;
|
41 |
+
}
|
42 |
+
|
43 |
+
export function Loading() {
|
44 |
+
return (
|
45 |
+
<div
|
46 |
+
style={{
|
47 |
+
height: "100vh",
|
48 |
+
width: "100vw",
|
49 |
+
display: "flex",
|
50 |
+
alignItems: "center",
|
51 |
+
justifyContent: "center",
|
52 |
+
}}
|
53 |
+
>
|
54 |
+
<LoadingIcon />
|
55 |
+
</div>
|
56 |
+
);
|
57 |
+
}
|
58 |
+
|
59 |
+
interface ModalProps {
|
60 |
+
title: string;
|
61 |
+
children?: JSX.Element;
|
62 |
+
actions?: JSX.Element[];
|
63 |
+
onClose?: () => void;
|
64 |
+
}
|
65 |
+
export function Modal(props: ModalProps) {
|
66 |
+
return (
|
67 |
+
<div className={styles["modal-container"]}>
|
68 |
+
<div className={styles["modal-header"]}>
|
69 |
+
<div className={styles["modal-title"]}>{props.title}</div>
|
70 |
+
|
71 |
+
<div className={styles["modal-close-btn"]} onClick={props.onClose}>
|
72 |
+
<CloseIcon />
|
73 |
+
</div>
|
74 |
+
</div>
|
75 |
+
|
76 |
+
<div className={styles["modal-content"]}>{props.children}</div>
|
77 |
+
|
78 |
+
<div className={styles["modal-footer"]}>
|
79 |
+
<div className={styles["modal-actions"]}>
|
80 |
+
{props.actions?.map((action, i) => (
|
81 |
+
<div key={i} className={styles["modal-action"]}>
|
82 |
+
{action}
|
83 |
+
</div>
|
84 |
+
))}
|
85 |
+
</div>
|
86 |
+
</div>
|
87 |
+
</div>
|
88 |
+
);
|
89 |
+
}
|
90 |
+
|
91 |
+
export function showModal(props: ModalProps) {
|
92 |
+
const div = document.createElement("div");
|
93 |
+
div.className = "modal-mask";
|
94 |
+
document.body.appendChild(div);
|
95 |
+
|
96 |
+
const root = createRoot(div);
|
97 |
+
const closeModal = () => {
|
98 |
+
props.onClose?.();
|
99 |
+
root.unmount();
|
100 |
+
div.remove();
|
101 |
+
};
|
102 |
+
|
103 |
+
div.onclick = (e) => {
|
104 |
+
if (e.target === div) {
|
105 |
+
closeModal();
|
106 |
+
}
|
107 |
+
};
|
108 |
+
|
109 |
+
root.render(<Modal {...props} onClose={closeModal}></Modal>);
|
110 |
+
}
|
111 |
+
|
112 |
+
export type ToastProps = { content: string };
|
113 |
+
|
114 |
+
export function Toast(props: ToastProps) {
|
115 |
+
return (
|
116 |
+
<div className={styles["toast-container"]}>
|
117 |
+
<div className={styles["toast-content"]}>{props.content}</div>
|
118 |
+
</div>
|
119 |
+
);
|
120 |
+
}
|
121 |
+
|
122 |
+
export function showToast(content: string, delay = 3000) {
|
123 |
+
const div = document.createElement("div");
|
124 |
+
div.className = styles.show;
|
125 |
+
document.body.appendChild(div);
|
126 |
+
|
127 |
+
const root = createRoot(div);
|
128 |
+
const close = () => {
|
129 |
+
div.classList.add(styles.hide);
|
130 |
+
|
131 |
+
setTimeout(() => {
|
132 |
+
root.unmount();
|
133 |
+
div.remove();
|
134 |
+
}, 300);
|
135 |
+
};
|
136 |
+
|
137 |
+
setTimeout(() => {
|
138 |
+
close();
|
139 |
+
}, delay);
|
140 |
+
|
141 |
+
root.render(<Toast content={content} />);
|
142 |
+
}
|
app/components/window.scss
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.window-header {
|
2 |
+
padding: 14px 20px;
|
3 |
+
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
4 |
+
|
5 |
+
display: flex;
|
6 |
+
justify-content: space-between;
|
7 |
+
align-items: center;
|
8 |
+
}
|
9 |
+
|
10 |
+
.window-header-title {
|
11 |
+
max-width: calc(100% - 100px);
|
12 |
+
|
13 |
+
.window-header-main-title {
|
14 |
+
font-size: 20px;
|
15 |
+
font-weight: bolder;
|
16 |
+
overflow: hidden;
|
17 |
+
text-overflow: ellipsis;
|
18 |
+
white-space: nowrap;
|
19 |
+
display: block;
|
20 |
+
max-width: 50vw;
|
21 |
+
}
|
22 |
+
|
23 |
+
.window-header-sub-title {
|
24 |
+
font-size: 14px;
|
25 |
+
margin-top: 5px;
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
.window-actions {
|
30 |
+
display: inline-flex;
|
31 |
+
}
|
32 |
+
|
33 |
+
.window-action-button {
|
34 |
+
margin-left: 10px;
|
35 |
+
}
|
app/constant.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export const OWNER = "Yidadaa";
|
2 |
+
export const REPO = "ChatGPT-Next-Web";
|
3 |
+
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
|
4 |
+
export const UPDATE_URL = `${REPO_URL}#%E4%BF%9D%E6%8C%81%E6%9B%B4%E6%96%B0-keep-updated`;
|
5 |
+
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
|
6 |
+
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
app/icons/add.svg
ADDED
|
app/icons/bot.svg
ADDED
|
app/icons/brain.svg
ADDED
|
app/icons/chat.svg
ADDED
|
app/icons/chatgpt.svg
ADDED
|
app/icons/clear.svg
ADDED
|
app/icons/close.svg
ADDED
|
app/icons/copy.svg
ADDED
|
app/icons/delete.svg
ADDED
|
app/icons/download.svg
ADDED
|
app/icons/edit.svg
ADDED
|
app/icons/export.svg
ADDED
|
app/icons/github.svg
ADDED
|
app/icons/menu.svg
ADDED
|
app/icons/reload.svg
ADDED
|
app/icons/send-white.svg
ADDED
|
app/icons/settings.svg
ADDED
|
app/icons/three-dots.svg
ADDED
|
app/icons/user.svg
ADDED
|