long123war hf4all commited on
Commit
d6c14d2
·
0 Parent(s):

Duplicate from hf4all/bingo

Browse files

Co-authored-by: hf4all <[email protected]>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +35 -0
  2. .editorconfig +36 -0
  3. .env.example +4 -0
  4. .eslintrc.json +3 -0
  5. .gitattributes +35 -0
  6. .gitignore +35 -0
  7. Dockerfile +37 -0
  8. LICENSE +21 -0
  9. README.md +149 -0
  10. docs/images/bing-cookie.png +0 -0
  11. docs/images/curl.png +0 -0
  12. docs/images/demo.png +0 -0
  13. next.config.js +36 -0
  14. package-lock.json +0 -0
  15. package.json +91 -0
  16. postcss.config.js +6 -0
  17. render.yaml +12 -0
  18. src/app/favicon.ico +0 -0
  19. src/app/globals.scss +1130 -0
  20. src/app/layout.tsx +47 -0
  21. src/app/loading.css +68 -0
  22. src/app/page.tsx +15 -0
  23. src/assets/images/brush.svg +5 -0
  24. src/assets/images/chat.svg +3 -0
  25. src/assets/images/check-mark.svg +3 -0
  26. src/assets/images/help.svg +3 -0
  27. src/assets/images/logo.svg +71 -0
  28. src/assets/images/pin-fill.svg +3 -0
  29. src/assets/images/pin.svg +3 -0
  30. src/assets/images/send.svg +3 -0
  31. src/assets/images/settings.svg +1 -0
  32. src/assets/images/speech.svg +18 -0
  33. src/assets/images/stop.svg +3 -0
  34. src/assets/images/visual-search.svg +3 -0
  35. src/assets/images/voice.svg +3 -0
  36. src/assets/images/warning.svg +3 -0
  37. src/components/button-scroll-to-bottom.tsx +34 -0
  38. src/components/chat-header.tsx +12 -0
  39. src/components/chat-list.tsx +28 -0
  40. src/components/chat-message.tsx +93 -0
  41. src/components/chat-notification.tsx +77 -0
  42. src/components/chat-panel.tsx +142 -0
  43. src/components/chat-scroll-anchor.tsx +29 -0
  44. src/components/chat-suggestions.tsx +45 -0
  45. src/components/chat.tsx +76 -0
  46. src/components/external-link.tsx +30 -0
  47. src/components/header.tsx +12 -0
  48. src/components/learn-more.tsx +39 -0
  49. src/components/markdown.tsx +9 -0
  50. src/components/providers.tsx +15 -0
.dockerignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
.editorconfig ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ # Use 4 spaces for the Python files
13
+ [*.py]
14
+ indent_size = 4
15
+ max_line_length = 80
16
+
17
+ # The JSON files contain newlines inconsistently
18
+ [*.json]
19
+ insert_final_newline = ignore
20
+
21
+ # Minified JavaScript files shouldn't be changed
22
+ [**.min.js]
23
+ indent_style = ignore
24
+ insert_final_newline = ignore
25
+
26
+ # Makefiles always use tabs for indentation
27
+ [Makefile]
28
+ indent_style = tab
29
+
30
+ # Batch files use tabs for indentation
31
+ [*.bat]
32
+ indent_style = tab
33
+
34
+ [*.md]
35
+ trim_trailing_whitespace = false
36
+
.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # 此配置全局生效,当用户没有配置用户信息时,此配置会做为默认配置,以下为示例配置,请根据实际情况修改。更多详情请参考 README.md
2
+
3
+ # 文档地址 https://github.com/weaigc/bingo/blob/main/README.md#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-bing_header
4
+ BING_HEADER=
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18
2
+
3
+
4
+ ARG DEBIAN_FRONTEND=noninteractive
5
+
6
+ ENV BING_UA Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.0.0
7
+ ENV BING_COOKIE ""
8
+
9
+ # Set up a new user named "user" with user ID 1000
10
+ RUN useradd -o -u 1000 user
11
+
12
+ # Switch to the "user" user
13
+ USER user
14
+
15
+ # Set home to the user's home directory
16
+ ENV HOME=/home/user \
17
+ PATH=/home/user/.local/bin:$PATH
18
+
19
+ # Set the working directory to the user's home directory
20
+ WORKDIR $HOME/app
21
+
22
+ # Install app dependencies
23
+ # A wildcard is used to ensure both package.json AND package-lock.json are copied
24
+ # where available (npm@5+)
25
+ COPY --chown=user package*.json $HOME/app
26
+
27
+ RUN npm install
28
+
29
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
30
+ COPY --chown=user . $HOME/app
31
+
32
+ RUN npm run build
33
+
34
+ ENV PORT 7860
35
+ EXPOSE 7860
36
+
37
+ CMD npm start -- --port $PORT
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License Copyright (c) 2023 weaigc
2
+
3
+ Permission is hereby granted, free
4
+ of charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
README.md ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: bingo
3
+ emoji: 📉
4
+ colorFrom: red
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: true
8
+ license: mit
9
+ duplicated_from: hf4all/bingo
10
+ ---
11
+
12
+ <div align="center">
13
+
14
+ # Bingo
15
+
16
+ Bingo,一个让你呼吸顺畅 New Bing。
17
+
18
+ 高度还原 New Bing 网页版的主要操作,国内可用,兼容绝大多数微软 Bing AI 的功能,可自行部署使用。
19
+
20
+ [![MIT License](https://img.shields.io/badge/license-MIT-97c50f)](https://github.com/weaigc/bingo/blob/main/license)
21
+
22
+
23
+ </div>
24
+
25
+ ## 演示站点
26
+
27
+ * 站点一:https://bing.github1s.tk (推荐)
28
+ * 站点二:https://effulgent-bubblegum-e2f5df.netlify.app (此站点部署到 Netlify 上,使用过程中可能需要认证)
29
+ * 站点三:https://bingo-beta-seven.vercel.app/ (此站点部署在 Vercel 上,由于[免费版本限制](https://vercel.com/docs/concepts/limits/overview),功能不一定正常,仅供参考)
30
+
31
+
32
+ [![img](./docs/images/demo.png)](https://bing.github1s.tk)
33
+
34
+ ## 功能和特点
35
+
36
+ - 完全基于 Next.js 重写,高度还原 New Bing Web 版 UI,使用体验和 Bing AI 基本一致。
37
+ - 支持 Docker 构建,方便快捷地部署和访问。
38
+ - Cookie 可全局配置,全局共享。
39
+ - 支持持续语音对话
40
+
41
+ ## RoadMap
42
+
43
+ - [x] 支持 wss 转发
44
+ - [x] 支持一键部署
45
+ - [x] 优化移动端展示
46
+ - [x] 支持画图
47
+ - [x] 支持语音输入(支持语音指令,目前仅支持 PC 版 Edge 及 Chrome 浏览器)
48
+ - [x] 支持语音输出(需要手动开启)
49
+ - [ ] 适配深色模式
50
+ - [ ] 支持内置提示词
51
+ - [ ] 支持图片输入
52
+ - [ ] 支持离线访问
53
+ - [ ] 国际化翻译
54
+
55
+ ## 一键部署
56
+ 你也可以一键部署自己的 New Bing AI 到 🤗 HuggingFace 。
57
+
58
+ ### 部署到 Huggingface
59
+ [![Deploy to HuggingFace](https://img.shields.io/badge/%E7%82%B9%E5%87%BB%E9%83%A8%E7%BD%B2-%F0%9F%A4%97-fff)](https://huggingface.co/login?next=%2Fspaces%2Fhf4all%2Fbingo%3Fduplicate%3Dtrue%26visibility%3Dpublic)
60
+
61
+ > Huggingface 不支持绑定自己的域名,不过我们可以使用曲线救国的方式来达到这个目的
62
+ 1. 方式一,借助 Github Pages 及 iframe [如何绑定域名](https://github.com/weaigc/bingo/issues/4)
63
+ 2. 方式二,Cloudflare Workers
64
+
65
+ ### 其它平台
66
+ 由于其他平台目前遭到 new bing 封杀,会遇到更多问题,不再做推荐,建议直接使用上面的方式。
67
+ #### 部署到 Netlify
68
+ [![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/weaigc/bingo)
69
+
70
+ #### 部署到 Vercel
71
+ 如果你是 Vercel 付费用户,可以点以下链接一键部署到 Vercel。免费版本有[接口超时限制](https://vercel.com/docs/concepts/limits/overview),不推荐使用
72
+
73
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-title=bingo&demo-description=bingo&demo-url=https%3A%2F%2Fbing.github1s.tk%2F&project-name=bingo&repository-name=bingo&repository-url=https%3A%2F%2Fgithub.com%2Fweaigc%2Fbingo&from=templates&skippable-integrations=1&env=BING_HEADER&envDescription=%E5%A6%82%E6%9E%9C%E4%B8%8D%E7%9F%A5%E9%81%93%E6%80%8E%E4%B9%88%E9%85%8D%E7%BD%AE%E8%AF%B7%E7%82%B9%E5%8F%B3%E4%BE%A7Learn+More&envLink=https%3A%2F%2Fgithub.com%2Fweaigc%2Fbingo%2Fblob%2Fmain%2F.env.example)
74
+
75
+ #### 部署到 Render
76
+
77
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/weaigc/bingo)
78
+
79
+
80
+ ## 环境和依赖
81
+
82
+ - Node.js >= 18
83
+ - Bing AI 的[身份信息](#如何获取-BING_HEADER))
84
+
85
+ ## 安装和使用
86
+
87
+ * 使用 Node 启动
88
+
89
+ ```bash
90
+ git clone https://github.com/weaigc/bingo.git
91
+ npm i # 推荐使用 pnpm i
92
+ npm run build
93
+ npm run start
94
+ ```
95
+
96
+ * 使用 Docker 启动
97
+ ```bash
98
+ git clone https://github.com/weaigc/bingo.git
99
+ docker build . -t bingo
100
+ docker run --rm -it -e BING_HEADER=xxxx -p 7860:7860 bingo
101
+ ```
102
+
103
+ ## 如何获取 BING_HEADER
104
+ 打开 https://www.bing.com 并登录,然后访问 https://www.bing.com/turing/conversation/create
105
+
106
+ ![BING HEADER](./docs/images/curl.png)
107
+
108
+
109
+ > 复制出来的内容应该如下所示。确认格式无误后,打开 https://effulgent-bubblegum-e2f5df.netlify.app/#dialog=%22settings%22 ,粘贴进去,点击“转成 BING_HEADER 并复制”,然后从剪切板粘贴即可得到。(你也可以先在网页上进行验证)
110
+ ```
111
+ curl 'https://www.bing.com/turing/conversation/create' \
112
+ -H 'authority: www.bing.com' \
113
+ -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
114
+ -H 'accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6' \
115
+ -H 'cache-control: max-age=0' \
116
+ -H 'cookie: MicrosoftApplicationsTelemetryDeviceId=3399c004-fd0e-48ec-bb92-d82a27b2bbd4; _EDGE_V=1; SRCHD=AF=NOFORM; SRCHUID=V=2&GUID=29EBDDA4E6674329ACCF1A0A423C3E98&dmnchg=1; _UR=QS=0&TQS=0; _HPVN=CS=eyJQbiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiUCJ9LCJTYyI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiSCJ9LCJReiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyMy0wNy0yNVQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIkRmdCI6bnVsbCwiTXZzIjowLCJGbHQiOjAsIkltcCI6Mn0=; _RwBf=ilt=1&ihpd=1&ispd=0&rc=0&rb=0&gb=0&rg=200&pc=0&mtu=0&rbb=0&g=0&cid=&clo=0&v=1&l=2023-07-25T07:00:00.0000000Z&lft=0001-01-01T00:00:00.0000000&aof=0&o=2&p=&c=&t=0&s=0001-01-01T00:00:00.0000000+00:00&ts=2023-07-25T11:00:31.7111548+00:00&rwred=0&wls=&lka=0&lkt=0&TH=&dci=0; ANON=A=0043C6590EA808ED6E395059FFFFFFFF&E=1c8b&W=1; NAP=V=1.9&E=1c31&C=DnaMSbDN_4efZ_xXqBF3Daorjr53kYqYoaP8YHsupjmiXnysX7a37A&W=1; PPLState=1; KievRPSSecAuth=FABSBBRaTOJILtFsMkpLVWSG6AN6C/svRwNmAAAEgAAACMGUA7EGVSjGEAQBGHtNsc5sNL7unmJsfPJ2t6imfo4BeUJlAia3IpMTtMUy4PU/C5QAzRI5pODtsIee0+blgllXt/5IiWwGjwmdhivsFM597pRPkjARPfwsPhNLPNbJrCPNPHdje4Is78MnCADXw6/NBq2FL8V2/byw2fH6IuAMD2MvN/VvqpEa9ZxiDjZtENj4HEj0mO2SgzjfyEhVAkjvznJqU2rw/Q2tHmX94NAM2kzlzKF/hWPhCCUmu8IHLvCnHDS6mSptvJDDP/sp3ovtzOXkP1mlM/Xju5ftesUvccVEQGffXORa1dE5hEMbKIiKXz1tDdduSXE19g9/+mRMAjaQhpwhI8XmilCTx1adb1Ll5qK+VjC9GNfEZzcbsGBPVaOl+anG8rEMq+Xnhjo7J+NqTNolavHgcuV8kJsCeJZIged33UA8eOZeFo+wAECMguxMoSqgpGH+sthqynvD/FJD6r/tiU2N3uqVq8NE8V37asrN6T14Z0FGBJOe6ET1+PGApm3s11OY9/xhFEB9T5BEPUGEbvRcLcW2ncFQX0EU+xweiPqo1Q1hNUg/dCtSI+lZ7c2H8XheePZavZ0TJQ8oNCSAuKiTqJmI0fVGpwbXwfaADkEipuawz3fIuMJBNgMU0OtA7Hm59v2fGLIBuvi6YeKS6GgVk3BIPf+P/eKahwozrxQZaFnoHTSqMkvct7xCP4atBROfXKf5Ww0CcFKp+2WX9BIskTOo2jjk6bAyyYJ+ElUB1fgLKNk5m/YSMc9iYCLIBMIGN8F0Yvy3tZ7cvh7Ue5Klo98US/I+nW1G7ZJMHRgUO8h8lpneHqEMegKd8gynO4VF7RpCjJkunDmW0Ta+RkXAP619pg0dqHMFkoOgknN78oBbGTV6fJUKotv+vi61kLhAeXZGWoHGCRXh2wUC6YgfPgKA6ESRNHtFn7E5B3HHpLc5rVMDSNhKZYfdhupV4Ezf6+5DhMcZLZhi0kk+ivDiN1gdHlVtSN55xpvf+c+XZDzR0uhgcvgy0LAbmzgk6y4WbYH+LQsMpzNNj+aC72vMiWovWrKh9jY4MYCmdgxsS/skPtLdp18muiEIRXTbZQGUmhxFpJAIbBIsCscMpzL0BgeujxUwM5wr79Sd9r4xwbgSMwmBlBfUHRVBdNyg8feepeJbCS63nD6eHOuLqMRsPIio3w/ki/EAa92UUEiZeavLsMUD/y/qAvWUdzdP5Y+C/TM+CMGS/kGL4LEdY/28MQeTvU1qv1X21kQt2aiaj3pPVL36hAzxbcLgqcMo9oymDRy87kdCXW/+g4oKLtMh6fm/G6W6Y/B01JlxohyyvueHQIG557uzkEkTJ3FnOVODSKBKpb3WZ65rExfV71zSZa25F3GmpaIG6HiYrX2YYhQAkIE9pKEQBHbnwHuwNDGottZTXZw=; WLS=C=9df3f9d8518fae19&N=wen; WLID=pGY8HgWCu4p5XYCOk2oa0+DBdftkMUfmNIn8XtSjSTKsgv/Il7GUlYs0Jpjf/E12jZMgV7x44Dy3fXOgjjUoJx7Y/ClLrLhsk20THksJJoI=; _EDGE_S=F=1&SID=17CF6EE006426448213C7DB907436588&mkt=zh-CN; MUID=225621093D8A6C27301632413C0E6D08; MUIDB=225621093D8A6C27301632413C0E6D08; SUID=A; SNRHOP=I=&TS=; _U=nGyzKQruEsDwLiu65fZFIG6e12hf2lwTJmroW__k8joUJIKmG3OIjayXKGW9dCVR3sNhF76mEVxyW6yjUGPodOfjtSa3s3J_DxMOrEK1BqXCOBI9bC66spAIASV7prsYFlVAJz73jVNENp_tBubLHJy6EbT0BKRe4AjrYkH-9uMnmCKB8Zmyg; _SS=SID=17CF6EE006426448213C7DB907436588&R=0&RB=0&GB=0&RG=200&RP=0&PC=U531; SRCHS=PC=U531; USRLOC=HS=1&ELOC=LAT=22.501529693603516|LON=113.9263687133789|N=%E5%8D%97%E5%B1%B1%E5%8C%BA%EF%BC%8C%E5%B9%BF%E4%B8%9C%E7%9C%81|ELT=2|&CLOC=LAT=22.50153029046461|LON=113.92637070632928|A=733.4464586120832|TS=230726151034|SRC=W; SRCHUSR=DOB=20230725&T=1690384908000&POEX=W; ipv6=hit=1690388509974&t=6; SRCHHPGUSR=HV=1690384945&SRCHLANG=zh-Hans&PV=15.0.0&BRW=MW&BRH=MT&CW=410&CH=794&SCW=410&SCH=794&DPR=1.5&UTC=480&DM=0&WTS=63825879627&PRVCW=410&PRVCH=794&PR=1.5; cct=AjWIBYOoVP-Afq6gWwtx80If6yHn6iBuEVHA1XHdAKpny6Y_CVyi_MSyM94VyMWnjdYkkccVtm3czoIAtXUGQA; GC=AjWIBYOoVP-Afq6gWwtx80If6yHn6iBuEVHA1XHdAKpR3Y_D9Ytcks4Ht6XhadXk75dvhzP4YOUS0UmoEyqyxw' \
117
+ -H 'dnt: 1' \
118
+ -H 'sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"' \
119
+ -H 'sec-ch-ua-arch: "x86"' \
120
+ -H 'sec-ch-ua-bitness: "64"' \
121
+ -H 'sec-ch-ua-full-version: "116.0.1938.29"' \
122
+ -H 'sec-ch-ua-full-version-list: "Chromium";v="116.0.5845.42", "Not)A;Brand";v="24.0.0.0", "Microsoft Edge";v="116.0.1938.29"' \
123
+ -H 'sec-ch-ua-mobile: ?0' \
124
+ -H 'sec-ch-ua-model: ""' \
125
+ -H 'sec-ch-ua-platform: "Windows"' \
126
+ -H 'sec-ch-ua-platform-version: "15.0.0"' \
127
+ -H 'sec-fetch-dest: document' \
128
+ -H 'sec-fetch-mode: navigate' \
129
+ -H 'sec-fetch-site: none' \
130
+ -H 'sec-fetch-user: ?1' \
131
+ -H 'sec-ms-gec: B3F47AD4A283CAB374C0451C46AAFD147C6A4DACAFF6A1C13F34B2C72B024494' \
132
+ -H 'sec-ms-gec-version: 1-116.0.1938.29' \
133
+ -H 'upgrade-insecure-requests: 1' \
134
+ -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.0.0' \
135
+ -H 'x-client-data: eyIxIjoiMiIsIjEwIjoiXCJTMGg3R05HOTF2aDQ1TUZSUnZ5NHN2akRmMWdlaVJKenNxNlA3aU1WbnF3PVwiIiwiMiI6IjEiLCIzIjoiMSIsIjQiOiIyMTU4ODQ5NTM4MjY4OTM5NTA3IiwiNSI6IlwiSm9GUWpPTDk3OS9MbkRRZnlCd2N1M2FsOUN3eTZTQmdaMGNYMXBtOWVMZz1cIiIsIjYiOiJiZXRhIiwiNyI6IjE4MDM4ODYyNjQzNSIsIjkiOiJkZXNrdG9wIn0=' \
136
+ -H 'x-edge-shopping-flag: 1' \
137
+ --compressed
138
+ ```
139
+
140
+
141
+ ## 鸣谢
142
+ - 感谢 [EdgeGPT](https://github.com/acheong08/EdgeGPT) 提供的代理 API 的方法。
143
+ - 感谢 [Vercel AI](https://github.com/vercel-labs/ai-chatbot) 提供的基础脚手架和 [ChatHub](https://github.com/chathub-dev/chathub) [go-proxy-bingai](https://github.com/adams549659584/go-proxy-bingai) 提供的部分代码。
144
+
145
+ ## License
146
+
147
+ MIT © [LICENSE](https://github.com/weaigc/bingo/blob/main/LICENSE).
148
+
149
+
docs/images/bing-cookie.png ADDED
docs/images/curl.png ADDED
docs/images/demo.png ADDED
next.config.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ // output: 'export',
4
+ // assetPrefix: '.',
5
+ webpack: (config, { isServer }) => {
6
+ if (!isServer) {
7
+ config.resolve = {
8
+ ...config.resolve,
9
+ fallback: {
10
+ http: false,
11
+ https: false,
12
+ stream: false,
13
+ // fixes proxy-agent dependencies
14
+ net: false,
15
+ dns: false,
16
+ tls: false,
17
+ assert: false,
18
+ // fixes next-i18next dependencies
19
+ path: false,
20
+ fs: false,
21
+ // fixes mapbox dependencies
22
+ events: false,
23
+ // fixes sentry dependencies
24
+ process: false
25
+ }
26
+ };
27
+ }
28
+ config.module.exprContextCritical = false;
29
+
30
+ return config;
31
+ },
32
+ }
33
+
34
+ module.exports = (...args) => {
35
+ return nextConfig
36
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bingo",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "cross-env DEBUG=bingo next dev --hostname 0.0.0.0",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@headlessui/react": "^1.7.15",
13
+ "@radix-ui/react-alert-dialog": "^1.0.4",
14
+ "@radix-ui/react-dialog": "^1.0.4",
15
+ "@radix-ui/react-dropdown-menu": "^2.0.5",
16
+ "@radix-ui/react-label": "^2.0.2",
17
+ "@radix-ui/react-select": "^1.2.2",
18
+ "@radix-ui/react-separator": "^1.0.3",
19
+ "@radix-ui/react-slot": "^1.0.2",
20
+ "@radix-ui/react-tooltip": "^1.0.6",
21
+ "autoprefixer": "10.4.14",
22
+ "cheerio": "^1.0.0-rc.12",
23
+ "class-variance-authority": "^0.7.0",
24
+ "clsx": "^2.0.0",
25
+ "debug": "^4.3.4",
26
+ "dotenv": "^16.3.1",
27
+ "eslint": "8.44.0",
28
+ "eslint-config-next": "13.4.9",
29
+ "http-proxy-middleware": "^2.0.6",
30
+ "https-proxy-agent": "^7.0.1",
31
+ "i18next": "^22.5.0",
32
+ "i18next-browser-languagedetector": "^7.0.2",
33
+ "idb-keyval": "^6.2.1",
34
+ "immer": "^9.0.19",
35
+ "inter-ui": "^3.19.3",
36
+ "jotai": "^2.2.1",
37
+ "jotai-immer": "^0.2.0",
38
+ "jotai-location": "^0.5.1",
39
+ "js-base64": "^3.7.5",
40
+ "lodash": "^4.17.21",
41
+ "lodash-es": "^4.17.21",
42
+ "nanoid": "^4.0.2",
43
+ "next": "13.4.9",
44
+ "next-auth": "^4.22.3",
45
+ "next-themes": "^0.2.1",
46
+ "postcss": "8.4.25",
47
+ "react": "18.2.0",
48
+ "react-dom": "18.2.0",
49
+ "react-hot-toast": "^2.4.1",
50
+ "react-intersection-observer": "^9.5.2",
51
+ "react-markdown": "^8.0.7",
52
+ "react-syntax-highlighter": "^15.5.0",
53
+ "react-textarea-autosize": "^8.5.0",
54
+ "react-viewport-list": "^7.1.1",
55
+ "rehype-highlight": "^6.0.0",
56
+ "rehype-stringify": "^9.0.3",
57
+ "remark": "^14.0.3",
58
+ "remark-breaks": "^3.0.3",
59
+ "remark-gfm": "^3.0.1",
60
+ "remark-math": "^5.1.1",
61
+ "remark-parse": "^10.0.2",
62
+ "remark-rehype": "^10.1.0",
63
+ "remark-supersub": "^1.0.0",
64
+ "tailwind-merge": "^1.14.0",
65
+ "tailwind-scrollbar": "^3.0.4",
66
+ "tailwindcss": "3.3.2",
67
+ "typescript": "5.1.6",
68
+ "undici": "^5.22.1",
69
+ "websocket-as-promised": "^2.0.1",
70
+ "ws": "^8.13.0"
71
+ },
72
+ "devDependencies": {
73
+ "@headlessui/tailwindcss": "^0.1.3",
74
+ "@types/debug": "^4.1.8",
75
+ "@types/dom-speech-recognition": "^0.0.1",
76
+ "@types/lodash-es": "^4.17.7",
77
+ "@types/md5": "^2.3.2",
78
+ "@types/node": "20.4.2",
79
+ "@types/react": "18.2.14",
80
+ "@types/react-color": "^3.0.6",
81
+ "@types/react-copy-to-clipboard": "^5.0.4",
82
+ "@types/react-dom": "18.2.7",
83
+ "@types/react-scroll-to-bottom": "^4.2.0",
84
+ "@types/react-syntax-highlighter": "^15.5.6",
85
+ "@types/ws": "^8.5.5",
86
+ "@typescript-eslint/eslint-plugin": "^5.60.1",
87
+ "@typescript-eslint/parser": "^5.60.1",
88
+ "cross-env": "^7.0.3",
89
+ "sass": "^1.62.1"
90
+ }
91
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
render.yaml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ - type: web
3
+ name: bingo
4
+ env: docker
5
+ autoDeploy: true
6
+ healthCheckPath: /api/healthz
7
+ plan: free
8
+ envVars:
9
+ - key: BING_HEADER
10
+ value:
11
+ - key: PORT
12
+ value: 10000
src/app/favicon.ico ADDED
src/app/globals.scss ADDED
@@ -0,0 +1,1130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --cib-color-foreground-accent-primary: #75306C;
7
+ --cib-color-foreground-accent-secondary: #692B61;
8
+ --cib-color-foreground-accent-tertiary: #5E2656;
9
+ --cib-color-foreground-accent-disabled: rgba(117, 48, 108, 0.3);
10
+ --cib-color-foreground-on-accent-primary: #FFFFFF;
11
+ --cib-color-foreground-on-accent-secondary: #FFF4F4;
12
+ --cib-color-foreground-on-accent-tertiary: #FFF4F4;
13
+ --cib-color-foreground-on-accent-disabled: rgba(255, 244, 244, 0.3);
14
+ --cib-color-foreground-neutral-primary: #111111;
15
+ --cib-color-foreground-neutral-secondary: #666666;
16
+ --cib-color-foreground-neutral-tertiary: #919191;
17
+ --cib-color-foreground-neutral-disabled: rgba(17, 17, 17, 0.4);
18
+ --cib-color-foreground-on-accent-strong-primary: #FFFFFF;
19
+ --cib-color-foreground-on-accent-strong-secondary: #FFFFFF;
20
+ --cib-color-foreground-on-accent-strong-disabled: rgba(255, 255, 255, 0.3);
21
+ --cib-color-foreground-system-attention-primary: #106EBE;
22
+ --cib-color-foreground-system-attribution-primary: #006621;
23
+ --cib-color-foreground-system-caution-primary: #9D5D00;
24
+ --cib-color-foreground-system-critical-primary: #C42B1C;
25
+ --cib-color-foreground-system-link-primary: #4007A2;
26
+ --cib-color-foreground-system-neutral-primary: rgba(0, 0, 0, 0.45);
27
+ --cib-color-foreground-system-success-primary: #0F7B0F;
28
+ --cib-color-fill-accent-primary: rgba(255, 255, 255, 0.7);
29
+ --cib-color-fill-accent-secondary: #FFF4F4;
30
+ --cib-color-fill-accent-tertiary: #FBE2E2;
31
+ --cib-color-fill-accent-disabled: rgba(255, 255, 255, 0.3);
32
+ --cib-color-fill-accent-alt-primary: #F6D0D0;
33
+ --cib-color-fill-accent-alt-secondary: #FFF4F4;
34
+ --cib-color-fill-accent-alt-tertiary: #FFF4F4;
35
+ --cib-color-fill-accent-alt-disabled: rgba(246, 208, 208, 0.3);
36
+ --cib-color-fill-accent-gradient-primary: linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
37
+ --cib-color-fill-accent-gradient-secondary: linear-gradient(0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)),
38
+ linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
39
+ --cib-color-fill-accent-gradient-tertiary: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
40
+ linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
41
+ --cib-color-fill-accent-gradient-quaternary: linear-gradient(90deg, rgb(238, 237, 243) 0%, 0.77381%, rgb(239, 238, 244) 1.54762%, 6.72619%, rgb(239, 236, 243) 11.9048%, 12.381%, rgb(240, 237, 244) 12.8571%, 27.9167%, rgb(242, 236, 244) 42.9762%, 51.9048%, rgb(239, 236, 243) 60.8333%, 61.9643%, rgb(238, 235, 246) 63.0952%, 66.7262%, rgb(235, 234, 249) 70.3571%, 73.2738%, rgb(232, 232, 248) 76.1905%, 77.1429%, rgb(230, 231, 248) 78.0952%, 79.9405%, rgb(228, 229, 249) 81.7857%, 84.1667%, rgb(227, 228, 248) 86.5476%, 87.0238%, rgb(226, 227, 248) 87.5%, 89.3452%, rgb(224, 224, 252) 91.1905%, 95.5952%, rgb(220, 223, 252) 100%);
42
+ --cib-color-fill-accent-strong-primary: #742F6B;
43
+ --cib-color-fill-accent-strong-secondary: #692B61;
44
+ --cib-color-fill-accent-strong-tertiary: #5E2656;
45
+ --cib-color-fill-accent-strong-disabled: rgba(116, 47, 107, 0.3);
46
+ --cib-color-fill-neutral-primary: #FFFFFF;
47
+ --cib-color-fill-neutral-secondary: #F9F9F9;
48
+ --cib-color-fill-neutral-tertiary: #F3F3F3;
49
+ --cib-color-fill-neutral-quaternary: transparent;
50
+ --cib-color-fill-neutral-disabled: rgba(255, 255, 255, 0.3);
51
+ --cib-color-fill-neutral-transparent: transparent;
52
+ --cib-color-fill-neutral-alt-primary: transparent;
53
+ --cib-color-fill-neutral-alt-secondary: rgba(0, 0, 0, 0.06);
54
+ --cib-color-fill-neutral-alt-tertiary: rgba(0, 0, 0, 0.09);
55
+ --cib-color-fill-neutral-alt-quaternary: rgba(0, 0, 0, 0.12);
56
+ --cib-color-fill-neutral-alt-disabled: transparent;
57
+ --cib-color-fill-neutral-alt-transparent: transparent;
58
+ --cib-color-fill-neutral-strong-primary: #444444;
59
+ --cib-color-fill-neutral-strong-secondary: #666666;
60
+ --cib-color-fill-neutral-strong-tertriary: #767676;
61
+ --cib-color-fill-neutral-strong-disabled: rgba(68, 68, 68, 0.3);
62
+ --cib-color-fill-subtle-primary: transparent;
63
+ --cib-color-fill-subtle-secondary: rgba(0, 0, 0, 0.06);
64
+ --cib-color-fill-subtle-tertiary: rgba(0, 0, 0, 0.1);
65
+ --cib-color-fill-subtle-quaternary: rgba(0, 0, 0, 0.2);
66
+ --cib-color-fill-subtle-disabled: transparent;
67
+ --cib-color-fill-subtle-transparent: transparent;
68
+ --cib-color-fill-subtle-alt-primary: rgba(0, 0, 0, 0.06);
69
+ --cib-color-fill-subtle-alt-secondary: rgba(0, 0, 0, 0.1);
70
+ --cib-color-fill-subtle-alt-tertiary: rgba(0, 0, 0, 0.2);
71
+ --cib-color-fill-accent-gradient-balanced-primary: linear-gradient(130deg, #2870EA 20%, #1B4AEF 77.5%);
72
+ --cib-color-fill-accent-gradient-balanced-secondary: linear-gradient(130deg, #2870EA 20%, #1B4AEF 77.5%),
73
+ linear-gradient(0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1));
74
+ --cib-color-fill-accent-gradient-balanced-tertiary: linear-gradient(130deg, #2870EA 20%, #1B4AEF 77.5%),
75
+ linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2));
76
+ --cib-color-fill-accent-gradient-balanced-quaternary: linear-gradient(90deg, rgb(239, 242, 247) 0%, 7.60286%, rgb(237, 240, 249) 15.2057%, 20.7513%, rgb(235, 239, 248) 26.297%, 27.6386%, rgb(235, 239, 248) 28.9803%, 38.2826%, rgb(231, 237, 249) 47.585%, 48.1216%, rgb(230, 236, 250) 48.6583%, 53.1306%, rgb(228, 236, 249) 57.6029%, 61.5385%, rgb(227, 234, 250) 65.4741%, 68.7835%, rgb(222, 234, 250) 72.093%, 75.7603%, rgb(219, 230, 248) 79.4275%, 82.8265%, rgb(216, 229, 248) 86.2254%, 87.8354%, rgb(213, 228, 249) 89.4454%, 91.8605%, rgb(210, 226, 249) 94.2755%, 95.4383%, rgb(209, 225, 248) 96.6011%, 98.3005%, rgb(208, 224, 247) 100%);
77
+ --cib-color-fill-accent-gradient-creative-primary: linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
78
+ --cib-color-fill-accent-gradient-creative-secondary: linear-gradient(0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)),
79
+ linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
80
+ --cib-color-fill-accent-gradient-creative-tertiary: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
81
+ linear-gradient(130deg, #914887 20%, #8B257E 77.5%);
82
+ --cib-color-fill-accent-gradient-creative-quaternary: linear-gradient(90deg, rgb(238, 237, 243) 0%, 0.77381%, rgb(239, 238, 244) 1.54762%, 6.72619%, rgb(239, 236, 243) 11.9048%, 12.381%, rgb(240, 237, 244) 12.8571%, 27.9167%, rgb(242, 236, 244) 42.9762%, 51.9048%, rgb(239, 236, 243) 60.8333%, 61.9643%, rgb(238, 235, 246) 63.0952%, 66.7262%, rgb(235, 234, 249) 70.3571%, 73.2738%, rgb(232, 232, 248) 76.1905%, 77.1429%, rgb(230, 231, 248) 78.0952%, 79.9405%, rgb(228, 229, 249) 81.7857%, 84.1667%, rgb(227, 228, 248) 86.5476%, 87.0238%, rgb(226, 227, 248) 87.5%, 89.3452%, rgb(224, 224, 252) 91.1905%, 95.5952%, rgb(220, 223, 252) 100%);
83
+ --cib-color-fill-accent-gradient-precise-primary: linear-gradient(130deg, #006880 20%, #005366 77.5%);
84
+ --cib-color-fill-accent-gradient-precise-secondary: linear-gradient(0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)),
85
+ linear-gradient(130deg, #006880 20%, #005366 77.5%);
86
+ --cib-color-fill-accent-gradient-precise-tertiary: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
87
+ linear-gradient(130deg, #006880 20%, #005366 77.5%);
88
+ --cib-color-fill-accent-gradient-precise-quaternary: linear-gradient(90deg, rgb(236, 242, 245) 0%, 1.3089%, rgb(234, 243, 245) 2.6178%, 17.4084%, rgb(232, 241, 242) 32.199%, 36.2565%, rgb(229, 241, 242) 40.3141%, 45.0262%, rgb(227, 240, 242) 49.7382%, 51.8325%, rgb(226, 239, 245) 53.9267%, 57.199%, rgb(224, 239, 245) 60.4712%, 62.9581%, rgb(220, 237, 245) 65.445%, 66.2304%, rgb(220, 237, 245) 67.0157%, 68.0628%, rgb(218, 236, 244) 69.1099%, 75.1309%, rgb(214, 233, 240) 81.1518%, 82.5916%, rgb(211, 231, 240) 84.0314%, 84.4241%, rgb(212, 231, 239) 84.8168%, 86.911%, rgb(210, 230, 239) 89.0052%, 94.5026%, rgb(207, 227, 236) 100%);
89
+ --cib-color-background-surface-app-primary: #FFFFFF;
90
+ --cib-color-background-surface-card-primary: rgba(255, 255, 255, 0.7);
91
+ --cib-color-background-surface-card-secondary: rgba(255, 255, 255, 0.4);
92
+ --cib-color-background-surface-card-tertiary: #FFFFFF;
93
+ --cib-color-background-surface-card-disabled: rgba(255, 255, 255, 0.4);
94
+ --cib-color-background-surface-smoke-primary: rgba(0, 0, 0, 0.5);
95
+ --cib-color-background-surface-solid-base: #F5F5F5;
96
+ --cib-color-background-surface-solid-secondary: #EEEEEE;
97
+ --cib-color-background-surface-solid-tertiary: #F9F9F9;
98
+ --cib-color-background-surface-solid-quaternary: #FFFFFF;
99
+ --cib-color-background-system-attention-primary: rgba(255, 255, 255, 0.5);
100
+ --cib-color-background-system-attention-strong: #106EBE;
101
+ --cib-color-background-system-success-primary: #DFF6DD;
102
+ --cib-color-background-system-success-strong: #0F7B0F;
103
+ --cib-color-background-system-caution-primary: #FFF4CE;
104
+ --cib-color-background-system-caution-strong: #9D5D00;
105
+ --cib-color-background-system-critical-primary: #FDE7E9;
106
+ --cib-color-background-system-critical-strong: #C42B1C;
107
+ --cib-color-stroke-accent-primary: #742F6B;
108
+ --cib-color-stroke-accent-secondary: #75306C;
109
+ --cib-color-stroke-accent-tertiary: #75306C;
110
+ --cib-color-stroke-accent-disabled: rgba(116, 47, 107, 0.3);
111
+ --cib-color-stroke-neutral-primary: rgba(0, 0, 0, 0.1);
112
+ --cib-color-stroke-neutral-secondary: rgba(0, 0, 0, 0.2);
113
+ --cib-color-stroke-neutral-tertiary: transparent;
114
+ --cib-color-stroke-neutral-alt-primary: rgba(0, 0, 0, 0.3);
115
+ --cib-color-stroke-surface-card-primary: transparent;
116
+ --cib-color-stroke-surface-card-solid: transparent;
117
+ --cib-color-stroke-surface-divider-primary: rgba(0, 0, 0, 0.1);
118
+ --cib-color-stroke-focus-outer: #111111;
119
+ --cib-color-stroke-focus-inner: #111111;
120
+ --cib-color-stroke-system-attention-primary: #106EBE;
121
+ --cib-color-stroke-system-success-primary: #0F7B0F;
122
+ --cib-color-stroke-system-caution-primary: #9D5D00;
123
+ --cib-color-stroke-system-critical-primary: #C42B1C;
124
+ --cib-color-stroke-system-neutral-primary: rgba(0, 0, 0, 0.45);
125
+ --cib-color-syntax-background-surface: rgba(0, 0, 0, 0.03);
126
+ --cib-color-syntax-background-green: #1B4721;
127
+ --cib-color-syntax-background-red: #78191B;
128
+ --cib-color-syntax-blue: #005CC5;
129
+ --cib-color-syntax-blue-strong: #032F62;
130
+ --cib-color-syntax-gold: #735C0F;
131
+ --cib-color-syntax-gray: #6A737D;
132
+ --cib-color-syntax-gray-strong: #24292E;
133
+ --cib-color-syntax-green: #22863A;
134
+ --cib-color-syntax-orange: #E36209;
135
+ --cib-color-syntax-purple: #6F42C1;
136
+ --cib-color-syntax-red: #D73A49;
137
+ --cib-color-syntax-red-strong: #B31D28;
138
+ --cib-action-bar-search-border-radius: 24px;
139
+ --cib-copy-host-border-radius: 8px;
140
+ --cib-copy-button-border-radius: 6px;
141
+ --cib-feedback-host-border-radius: 8px;
142
+ --cib-feedback-menu-border-radius: 8px;
143
+ --cib-feedback-menu-before-border-radius: 9px;
144
+ --cib-feedback-button-border-radius: 6px;
145
+ --cib-flyout-host-border-radius: 6px;
146
+ --cib-message-ac-container-border-radius: 3px;
147
+ --cib-modal-before-border-radius: 13px;
148
+ --cib-side-panel-aad-msa-redirect-border-radius: 9px;
149
+ --cib-thread-host-border-radius: 6px;
150
+ --cib-thread-host-preview-border-radius: 8px;
151
+ --cib-thread-name-border-radius: 3px;
152
+ --cib-tooltip-host-before-border-radius: 5px;
153
+ --cib-welcome-container-preview-button-border-radius: 3px;
154
+ --cib-color-icon-red-cancel: #c80000;
155
+ --cib-color-icon-green-confirm: #13a10e;
156
+ --cib-image-background: url(https://bing.vcanbb.top/cdx/bg.jpg);
157
+ --cib-shadow-card: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12),
158
+ 0px 1.6px 3.6px rgba(0, 0, 0, 0.16);
159
+ --cib-shadow-card-raised: 0px 0.6px 1.8px rgba(0, 0, 0, 0.12),
160
+ 0px 3.2px 7.2px rgba(0, 0, 0, 0.16);
161
+ --cib-shadow-dialog: 0px 4.8px 14.4px rgba(0, 0, 0, 0.18),
162
+ 0px 25.6px 57.6px rgba(0, 0, 0, 0.22);
163
+ --cib-shadow-flyout: 0px 1.2px 3.6px rgba(0, 0, 0, 0.16),
164
+ 0px 6.4px 14.4px rgba(0, 0, 0, 0.2);
165
+ --cib-shadow-layer: 0px 0.15px 0.45px rgba(0, 0, 0, 0.12),
166
+ 0px 0.8px 1.8px rgba(0, 0, 0, 0.16);
167
+ --cib-shadow-panel: 0px 14px 28px rgba(0, 0, 0, 0.24),
168
+ 0px 0px 8px rgba(0, 0, 0, 0.2);
169
+ --cib-shadow-tooltip: 0px 1.2px 3.6px rgba(0, 0, 0, 0.16),
170
+ 0px 6.4px 14.4px rgba(0, 0, 0, 0.2);
171
+ --cib-shadow-elevation-1: 0px 0.075px 0.225px rgba(0, 0, 0, 0.12),
172
+ 0px 0.4px 0.9px rgba(0, 0, 0, 0.16);
173
+ --cib-shadow-elevation-2: 0px 0.15px 0.45px rgba(0, 0, 0, 0.12),
174
+ 0px 0.8px 1.8px rgba(0, 0, 0, 0.16);
175
+ --cib-shadow-elevation-4: 0px 0.3px 0.9px rgba(0, 0, 0, 0.12),
176
+ 0px 1.6px 3.6px rgba(0, 0, 0, 0.16);
177
+ --cib-shadow-elevation-8: 0px 0.6px 1.8px rgba(0, 0, 0, 0.12),
178
+ 0px 3.2px 7.2px rgba(0, 0, 0, 0.16);
179
+ --cib-shadow-elevation-16: 0px 1.2px 3.6px rgba(0, 0, 0, 0.16),
180
+ 0px 6.4px 14.4px rgba(0, 0, 0, 0.2);
181
+ --cib-shadow-elevation-28: 0px 14px 28px rgba(0, 0, 0, 0.24),
182
+ 0px 0px 8px rgba(0, 0, 0, 0.2);
183
+ --cib-shadow-elevation-64: 0px 4.8px 14.4px rgba(0, 0, 0, 0.18),
184
+ 0px 25.6px 57.6px rgba(0, 0, 0, 0.22);
185
+ --cib-border-radius-none: 0;
186
+ --cib-border-radius-small: 2px;
187
+ --cib-border-radius-medium: 4px;
188
+ --cib-border-radius-large: 8px;
189
+ --cib-border-radius-extra-large: 12px;
190
+ --cib-border-radius-circular: 10000px;
191
+ --cib-font-text: -apple-system,
192
+ Roboto,
193
+ SegoeUI,
194
+ 'Segoe UI',
195
+ 'Helvetica Neue',
196
+ Helvetica,
197
+ 'Microsoft YaHei',
198
+ 'Meiryo UI',
199
+ Meiryo,
200
+ Arial Unicode MS,
201
+ sans-serif;
202
+ --cib-font-icons: 'Fluent Icons';
203
+ --cib-type-caption2-font-size: 10px;
204
+ --cib-type-caption2-line-height: 14px;
205
+ --cib-type-caption2-font-weight: 400;
206
+ --cib-type-caption2-font-variation-settings: unset;
207
+ --cib-type-caption2-strong-font-size: 10px;
208
+ --cib-type-caption2-strong-line-height: 14px;
209
+ --cib-type-caption2-strong-font-weight: 600;
210
+ --cib-type-caption2-strong-font-variation-settings: unset;
211
+ --cib-type-caption1-font-size: 12px;
212
+ --cib-type-caption1-line-height: 16px;
213
+ --cib-type-caption1-font-weight: 400;
214
+ --cib-type-caption1-font-variation-settings: unset;
215
+ --cib-type-caption1-strong-font-size: 12px;
216
+ --cib-type-caption1-strong-line-height: 16px;
217
+ --cib-type-caption1-strong-font-weight: 600;
218
+ --cib-type-caption1-strong-font-variation-settings: unset;
219
+ --cib-type-caption1-stronger-font-size: 12px;
220
+ --cib-type-caption1-stronger-line-height: 16px;
221
+ --cib-type-caption1-stronger-font-weight: 700;
222
+ --cib-type-caption1-stronger-font-variation-settings: unset;
223
+ --cib-type-body1-font-size: 14px;
224
+ --cib-type-body1-line-height: 20px;
225
+ --cib-type-body1-font-weight: 400;
226
+ --cib-type-body1-font-variation-settings: unset;
227
+ --cib-type-body1-strong-font-size: 14px;
228
+ --cib-type-body1-strong-line-height: 20px;
229
+ --cib-type-body1-strong-font-weight: 500;
230
+ --cib-type-body1-strong-font-variation-settings: unset;
231
+ --cib-type-body1-stronger-font-size: 14px;
232
+ --cib-type-body1-stronger-line-height: 20px;
233
+ --cib-type-body1-stronger-font-weight: 600;
234
+ --cib-type-body1-stronger-font-variation-settings: unset;
235
+ --cib-type-body2-font-size: 16px;
236
+ --cib-type-body2-line-height: 24px;
237
+ --cib-type-body2-font-weight: 400;
238
+ --cib-type-body2-font-variation-settings: unset;
239
+ --cib-type-subtitle2-font-size: 16px;
240
+ --cib-type-subtitle2-line-height: 24px;
241
+ --cib-type-subtitle2-font-weight: 500;
242
+ --cib-type-subtitle2-font-variation-settings: unset;
243
+ --cib-type-subtitle2-stronger-font-size: 16px;
244
+ --cib-type-subtitle2-stronger-line-height: 24px;
245
+ --cib-type-subtitle2-stronger-font-weight: 600;
246
+ --cib-type-subtitle2-stronger-font-variation-settings: unset;
247
+ --cib-type-subtitle1-font-size: 20px;
248
+ --cib-type-subtitle1-line-height: 26px;
249
+ --cib-type-subtitle1-font-weight: 500;
250
+ --cib-type-subtitle1-font-variation-settings: unset;
251
+ --cib-type-subtitle1-stronger-font-size: 20px;
252
+ --cib-type-subtitle1-stronger-line-height: 26px;
253
+ --cib-type-subtitle1-stronger-font-weight: 600;
254
+ --cib-type-subtitle1-stronger-font-variation-settings: unset;
255
+ --cib-type-message-font-size: 18px;
256
+ --cib-type-message-line-height: 24px;
257
+ --cib-type-message-font-weight: 400;
258
+ --cib-type-message-font-variation-settings: unset;
259
+ --cib-type-message-strong-font-size: 18px;
260
+ --cib-type-message-strong-line-height: 24px;
261
+ --cib-type-message-strong-font-weight: 600;
262
+ --cib-type-message-strong-font-variation-settings: unset;
263
+ --cib-type-title3-font-size: 24px;
264
+ --cib-type-title3-line-height: 32px;
265
+ --cib-type-title3-font-weight: 600;
266
+ --cib-type-title3-font-variation-settings: unset;
267
+ --cib-type-title2-font-size: 28px;
268
+ --cib-type-title2-line-height: 36px;
269
+ --cib-type-title2-font-weight: 600;
270
+ --cib-type-title2-font-variation-settings: unset;
271
+ --cib-type-title1-font-size: 32px;
272
+ --cib-type-title1-line-height: 40px;
273
+ --cib-type-title1-font-weight: 600;
274
+ --cib-type-title1-font-variation-settings: unset;
275
+ --cib-type-large-title-font-size: 40px;
276
+ --cib-type-large-title-line-height: 52px;
277
+ --cib-type-large-title-font-weight: 600;
278
+ --cib-type-large-title-font-variation-settings: unset;
279
+ --cib-type-display-font-size: 68px;
280
+ --cib-type-display-line-height: 92px;
281
+ --cib-type-display-font-weight: 600;
282
+ --cib-type-display-font-variation-settings: unset;
283
+ --cib-motion-duration-faster: 83ms;
284
+ --cib-motion-duration-fast: 187ms;
285
+ --cib-motion-duration-normal: 333ms;
286
+ --cib-motion-duration-slow: 500ms;
287
+ --cib-motion-duration-slower: 667ms;
288
+ --cib-motion-duration-slowest: 1000ms;
289
+ --cib-motion-duration-faster-number: 83;
290
+ --cib-motion-duration-fast-number: 187;
291
+ --cib-motion-duration-normal-number: 333;
292
+ --cib-motion-duration-slow-number: 500;
293
+ --cib-motion-duration-slower-number: 667;
294
+ --cib-motion-duration-slowest-number: 1000;
295
+ --cib-motion-easing-linear: cubic-bezier(0, 0, 1, 1);
296
+ --cib-motion-easing-in: cubic-bezier(0, 0, 0, 1);
297
+ --cib-motion-easing-out: cubic-bezier(1, 0, 1, 1);
298
+ --cib-motion-easing-strong: cubic-bezier(0.13, 1.62, 0, 0.92);
299
+ --cib-motion-easing-direct: cubic-bezier(0.55, 0.55, 0, 1);
300
+ --cib-motion-easing-transition: cubic-bezier(0.75, 0, 0.25, 1);
301
+ --button-compose-collapsed-width: 48px;
302
+ --button-compose-expanded-width: 116px;
303
+ font-family: var(--cib-font-text);
304
+ }
305
+
306
+ @media (prefers-color-scheme: dark) {
307
+ html {
308
+ color-scheme: light !important;
309
+ }
310
+ }
311
+
312
+ body {
313
+ background: var(--cib-color-fill-accent-gradient-creative-quaternary);
314
+ }
315
+
316
+ .bg-background {
317
+ background: var(--cib-color-background-surface-app-primary);
318
+ }
319
+
320
+ main {
321
+ margin: 0 auto;
322
+ position: relative;
323
+ width: calc(100% - var(--side-panel-width));
324
+ }
325
+
326
+ :root {
327
+ --side-panel-width: 280px;
328
+ }
329
+
330
+ @media (max-width: 767px) {
331
+ :root {
332
+ --side-panel-width: 16px;
333
+ }
334
+ }
335
+
336
+ .chat-container,
337
+ .suggestion-items {
338
+ max-width: 1120px;
339
+ margin: 0 auto;
340
+ }
341
+
342
+ .welcome-container {
343
+ display: flex;
344
+ flex-direction: row;
345
+ flex-wrap: wrap;
346
+ align-items: center;
347
+ height: 100%;
348
+ gap: 24px;
349
+ justify-content: center;
350
+ }
351
+
352
+ .welcome-item {
353
+ display: flex;
354
+ flex-direction: column;
355
+ align-items: center;
356
+ gap: 8px;
357
+ background: transparent;
358
+ border: none;
359
+ font-family: var(--cib-font-text);
360
+ }
361
+
362
+ .item-title {
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ text-align: center;
367
+ min-height: 52px;
368
+ color: var(--cib-color-foreground-neutral-primary);
369
+ font-family: var(--cib-font-text);
370
+ font-size: var(--cib-type-message-strong-font-size);
371
+ line-height: var(--cib-type-message-strong-line-height);
372
+ font-weight: var(--cib-type-message-strong-font-weight);
373
+ font-variation-settings: var(--cib-type-message-strong-font-variation-settings);
374
+ }
375
+
376
+ .item-content {
377
+ display: flex;
378
+ align-items: center;
379
+ gap: 4px;
380
+ position: relative;
381
+ height: 100%;
382
+ background: var(--cib-color-background-surface-card-primary);
383
+ border-radius: var(--cib-border-radius-medium);
384
+ text-align: start;
385
+ outline: transparent solid 1px;
386
+ box-sizing: border-box;
387
+ padding: 20px;
388
+ cursor: pointer;
389
+ }
390
+
391
+ .item-content::before {
392
+ content: "";
393
+ position: absolute;
394
+ width: 100%;
395
+ height: 100%;
396
+ top: 0px;
397
+ left: 0px;
398
+ z-index: -1;
399
+ opacity: 0;
400
+ background: var(--cib-color-background-surface-card-primary);
401
+ border-radius: var(--cib-border-radius-medium);
402
+ transition-property: opacity;
403
+ transition-duration: var(--cib-motion-duration-fast);
404
+ transition-timing-function: var(--cib-motion-easing-transition);
405
+ box-shadow: var(--cib-shadow-card);
406
+ }
407
+
408
+ .item-content:hover::before {
409
+ opacity: 1;
410
+ }
411
+
412
+ .item-body {
413
+ color: var(--cib-color-foreground-neutral-primary);
414
+ align-items: center;
415
+ display: flex;
416
+ flex-direction: column;
417
+ font-family: var(--cib-font-text);
418
+ font-size: var(--cib-type-body2-font-size);
419
+ line-height: var(--cib-type-body2-line-height);
420
+ font-weight: var(--cib-type-body2-font-weight);
421
+ font-variation-settings: var(--cib-type-body2-font-variation-settings);
422
+ }
423
+
424
+ .fieldset {
425
+ margin: 48px auto;
426
+ padding: 0px;
427
+ border: none;
428
+ width: 310px;
429
+ transition-property: opacity;
430
+ transition-duration: var(--cib-motion-duration-fast);
431
+ transition-timing-function: var(--cib-motion-easing-transition);
432
+ }
433
+
434
+ .legend {
435
+ width: 100%;
436
+ display: flex;
437
+ justify-content: center;
438
+ align-items: center;
439
+ }
440
+
441
+ .caption-2-strong {
442
+ font-size: var(--cib-type-caption2-strong-font-size);
443
+ line-height: var(--cib-type-caption2-strong-line-height);
444
+ font-weight: var(--cib-type-caption2-strong-font-weight);
445
+ font-variation-settings: var(--cib-type-caption2-strong-font-variation-settings);
446
+ }
447
+
448
+ .label-modifier {
449
+ display: block;
450
+ margin-bottom: -2px;
451
+ }
452
+
453
+ .options-list-container {
454
+ padding: 3px;
455
+ margin: 16px 0px;
456
+ border-radius: var(--cib-border-radius-large);
457
+ background: var(--cib-color-background-surface-card-primary);
458
+ box-shadow: var(--cib-shadow-card);
459
+ }
460
+
461
+ .options {
462
+ display: grid;
463
+ grid-auto-columns: 1fr;
464
+ grid-auto-flow: column;
465
+ padding: 0px;
466
+ margin: 0px;
467
+ list-style: none;
468
+ }
469
+
470
+ .option {
471
+ display: inline-block;
472
+ min-width: 96px;
473
+ height: 42px;
474
+ padding: 0px;
475
+ outline: transparent solid 1px;
476
+ border-radius: var(--cib-border-radius-medium);
477
+ }
478
+
479
+ .option button {
480
+ position: relative;
481
+ display: flex;
482
+ flex-direction: column;
483
+ align-items: center;
484
+ justify-content: center;
485
+ width: 100%;
486
+ height: 100%;
487
+ padding: 0px 8px;
488
+ border: none;
489
+ border-radius: var(--cib-border-radius-medium);
490
+ background: transparent;
491
+ cursor: pointer;
492
+ font-family: var(--cib-font-text);
493
+ }
494
+
495
+ .option button.selected {
496
+ color: var(--cib-color-foreground-on-accent-primary);
497
+ background: var(--cib-color-fill-accent-gradient-primary);
498
+ }
499
+
500
+ .text-message {
501
+ position: relative;
502
+ display: flex;
503
+ flex-direction: column;
504
+ max-width: min(768px, 100%);
505
+ margin-inline-end: 80px;
506
+ width: fit-content;
507
+ opacity: 1;
508
+ z-index: 10;
509
+ outline: transparent solid 1px;
510
+ box-shadow: var(--cib-shadow-card);
511
+ border-radius: var(--cib-border-radius-extra-large);
512
+ background: var(--cib-color-background-surface-card-primary);
513
+ }
514
+
515
+ .text-message {
516
+ &.user {
517
+ align-items: flex-end;
518
+ align-self: flex-end;
519
+ margin-inline-end: unset;
520
+ margin-inline-start: 80px;
521
+ z-index: 10;
522
+ background: var(--cib-color-fill-accent-gradient-primary);
523
+ box-shadow: var(--cib-shadow-elevation-4);
524
+ color: var(--cib-color-foreground-on-accent-primary);
525
+ }
526
+
527
+ &.bot {
528
+ a {
529
+ color: var(--cib-color-foreground-system-link-primary);
530
+ }
531
+ }
532
+
533
+ a {
534
+ position: relative;
535
+ text-decoration: none;
536
+ }
537
+ }
538
+
539
+
540
+ .text-message-content {
541
+ display: flex;
542
+ flex-direction: column;
543
+ padding: 10px 16px 4px 16px;
544
+ user-select: text;
545
+ word-break: break-word;
546
+ min-height: var(--cib-type-body2-line-height);
547
+ font-size: var(--cib-type-body2-font-size);
548
+ line-height: var(--cib-type-body2-line-height);
549
+ font-weight: var(--cib-type-body2-font-weight);
550
+ font-variation-settings: var(--cib-type-body2-font-variation-settings);
551
+ overflow: hidden;
552
+
553
+ h1 {
554
+ font-size: var(--cib-type-title2-font-size);
555
+ line-height: var(--cib-type-title2-line-height);
556
+ font-weight: var(--cib-type-title2-font-weight);
557
+ font-variation-settings: var(--cib-type-title2-font-variation-settings);
558
+ }
559
+
560
+ p,
561
+ h1,
562
+ h2,
563
+ h3,
564
+ h4,
565
+ pre {
566
+ padding: 0px;
567
+ user-select: text;
568
+ word-break: break-word;
569
+ display: inline-block;
570
+ }
571
+
572
+ ol,
573
+ menu {
574
+ list-style: decimal;
575
+ margin: 0;
576
+ padding: 0;
577
+ padding-inline-start: 24px;
578
+ }
579
+
580
+ ul,
581
+ ol {
582
+ display: flex;
583
+ flex-direction: column;
584
+ gap: 10px;
585
+ padding-inline-start: 24px;
586
+ }
587
+
588
+ ul {
589
+ list-style: disc;
590
+ }
591
+
592
+ >*:nth-child(n+2) {
593
+ margin-top: 12px;
594
+ }
595
+
596
+ .codeblock {
597
+ border-radius: var(--cib-border-radius-large);
598
+ overflow: hidden;
599
+ }
600
+
601
+ blockquote>p>img {
602
+ max-width: 50%;
603
+ float: left;
604
+ }
605
+ }
606
+
607
+ table,
608
+ ul,
609
+ ol,
610
+ p {
611
+ padding-bottom: 12px;
612
+ }
613
+
614
+ .text-message-footer {
615
+ display: grid;
616
+ grid-template-columns: 1fr auto;
617
+ justify-content: space-between;
618
+
619
+ border-top: 1px solid var(--cib-color-stroke-neutral-primary);
620
+ padding: 0px;
621
+ align-items: self-start;
622
+ }
623
+
624
+ .learn-more-root {
625
+ display: flex;
626
+ flex-direction: row;
627
+ row-gap: 8px;
628
+ padding: 0px 16px;
629
+ margin: 9px 0px;
630
+ overflow: hidden;
631
+ }
632
+
633
+ @media (max-width: 600px) {
634
+ .learn-more-root {
635
+ flex-wrap: wrap;
636
+ }
637
+ }
638
+
639
+ .learn-more {
640
+ position: relative;
641
+ align-self: flex-start;
642
+ min-width: fit-content;
643
+ top: 2px;
644
+ inset-inline-start: 1px;
645
+ margin-inline-end: 8px;
646
+ font-size: var(--cib-type-body1-stronger-font-size);
647
+ line-height: var(--cib-type-body1-stronger-line-height);
648
+ font-weight: var(--cib-type-body1-stronger-font-weight);
649
+ font-variation-settings: var(--cib-type-body1-stronger-font-variation-settings);
650
+ }
651
+
652
+ .attribution-container {
653
+ display: flex;
654
+ flex-direction: row;
655
+ row-gap: 6px;
656
+ }
657
+
658
+ .attribution-items {
659
+ display: flex;
660
+ flex-flow: wrap;
661
+ row-gap: 6px;
662
+ }
663
+
664
+ .attribution-item {
665
+ cursor: pointer;
666
+ text-decoration: none;
667
+ display: flex;
668
+ align-items: center;
669
+ justify-content: center;
670
+ min-width: max-content;
671
+ height: 24px;
672
+ border-radius: var(--cib-border-radius-medium);
673
+ box-sizing: border-box;
674
+ padding: 0px 8px;
675
+ margin-inline-end: 6px;
676
+ color: var(--cib-color-foreground-accent-primary);
677
+ background: var(--cib-color-fill-accent-alt-primary);
678
+ font-family: var(--cib-font-text);
679
+ font-size: var(--cib-type-body1-strong-font-size);
680
+ line-height: var(--cib-type-body1-strong-line-height);
681
+ font-weight: var(--cib-type-body1-strong-font-weight);
682
+ font-variation-settings: var(--cib-type-body1-strong-font-variation-settings);
683
+ }
684
+
685
+ .turn-counter {
686
+ display: flex;
687
+ flex-shrink: 0;
688
+ flex-direction: row;
689
+ align-items: center;
690
+ gap: 6px;
691
+ margin-inline-start: 12px;
692
+ grid-area: 1 / 2 / 2 / 3;
693
+ margin: 9px 14px;
694
+
695
+ .text {
696
+ display: flex;
697
+ gap: 3px;
698
+ font-size: var(--cib-type-body1-stronger-font-size);
699
+ line-height: var(--cib-type-body1-stronger-line-height);
700
+ font-weight: var(--cib-type-body1-stronger-font-weight);
701
+ font-variation-settings: var(--cib-type-body1-stronger-font-variation-settings);
702
+ }
703
+
704
+ .indicator {
705
+ width: 12px;
706
+ height: 12px;
707
+ border-radius: var(--cib-border-radius-circular);
708
+ background: rgb(44, 130, 71);
709
+ }
710
+ }
711
+
712
+ @media (max-width: 600px) {
713
+ .turn-counter {
714
+ inset-inline-end: 0px;
715
+ }
716
+ }
717
+
718
+ @media (max-width: 767px) {
719
+ .suggestion-items {
720
+ display: contents;
721
+ }
722
+ }
723
+
724
+ .suggestion-items {
725
+ display: flex;
726
+ align-items: center;
727
+ justify-content: flex-end;
728
+ flex-flow: wrap;
729
+ gap: 8px 8px;
730
+ order: 1;
731
+ padding-inline-end: 2px;
732
+ overflow: hidden;
733
+ }
734
+
735
+ .suggestion-container {
736
+ height: 30px;
737
+ min-width: max-content;
738
+ overflow: hidden;
739
+ box-sizing: border-box;
740
+ padding: 0px 12px;
741
+ margin: 1px;
742
+ cursor: pointer;
743
+ border: 1px solid var(--cib-color-stroke-accent-primary);
744
+ color: var(--cib-color-foreground-accent-primary);
745
+ background: var(--cib-color-fill-accent-primary);
746
+ border-radius: var(--cib-border-radius-large);
747
+ font-family: var(--cib-font-text);
748
+ font-size: var(--cib-type-body1-strong-font-size);
749
+ line-height: var(--cib-type-body1-strong-line-height);
750
+ font-weight: var(--cib-type-body1-strong-font-weight);
751
+ font-variation-settings: var(--cib-type-body1-strong-font-variation-settings);
752
+
753
+ &:hover,
754
+ &:focus {
755
+ background: var(--cib-color-fill-accent-secondary);
756
+ border-color: var(--cib-color-stroke-accent-secondary);
757
+ color: var(--cib-color-foreground-accent-secondary);
758
+ }
759
+ }
760
+
761
+ .typing-control-item {
762
+ position: relative;
763
+ display: flex;
764
+ flex-direction: row;
765
+ align-items: center;
766
+ cursor: pointer;
767
+ justify-content: center;
768
+ background: var(--cib-color-fill-accent-secondary);
769
+ border-radius: var(--cib-border-radius-large);
770
+ height: 40px;
771
+ box-sizing: border-box;
772
+ padding: 0px 8px;
773
+ color: var(--cib-color-foreground-accent-primary);
774
+ fill: var(--cib-color-foreground-accent-primary);
775
+ border: 1px solid var(--cib-color-stroke-accent-primary);
776
+ font-family: var(--cib-font-text);
777
+ font-size: var(--cib-type-subtitle2-font-size);
778
+ line-height: var(--cib-type-subtitle2-line-height);
779
+ font-weight: var(--cib-type-subtitle2-font-weight);
780
+ font-variation-settings: var(--cib-type-subtitle2-font-variation-settings);
781
+
782
+ &>.stop {
783
+ gap: 2px;
784
+ padding: 0px 12px;
785
+ }
786
+ }
787
+
788
+ .notification-container {
789
+ align-items: flex-end;
790
+ justify-content: center;
791
+ width: 100%;
792
+ transition-property: transform, max-width, min-width;
793
+ transition-duration: var(--cib-motion-duration-slowest);
794
+ transition-timing-function: var(--cib-motion-easing-transition);
795
+
796
+ .bottom-notifications {
797
+ display: flex;
798
+ align-items: center;
799
+ justify-content: center;
800
+ width: 100%;
801
+ margin: 60px 0px 0px;
802
+ }
803
+
804
+ .inline-type {
805
+ display: flex;
806
+ justify-content: center;
807
+ align-items: center;
808
+ text-align: center;
809
+ width: 100%;
810
+ max-width: 1120px;
811
+ color: var(--cib-color-foreground-neutral-primary);
812
+ font-size: var(--cib-type-body2-font-size);
813
+ line-height: var(--cib-type-body2-line-height);
814
+ font-weight: var(--cib-type-body2-font-weight);
815
+ font-variation-settings: var(--cib-type-body2-font-variation-settings);
816
+
817
+ &.with-decorative-line {
818
+ &::before {
819
+ margin-inline-end: 1vw;
820
+ }
821
+
822
+ &::before,
823
+ &::after {
824
+ content: "";
825
+ flex: 1 1 0%;
826
+ border-bottom: 1px solid var(--cib-color-stroke-neutral-primary);
827
+ }
828
+ }
829
+
830
+ .text-container {
831
+ max-width: 80%;
832
+ padding: 0px 10px;
833
+ align-items: center;
834
+ }
835
+
836
+ .title {
837
+ position: relative;
838
+ color: var(--cib-color-foreground-neutral-primary);
839
+
840
+ a {
841
+ color: var(--cib-color-foreground-system-link-primary);
842
+ }
843
+ }
844
+ }
845
+ }
846
+
847
+ @media (max-width: 767px) {
848
+ .inline-type {
849
+ margin-bottom: unset;
850
+ }
851
+ }
852
+
853
+ .action-bar {
854
+ position: fixed;
855
+ display: flex;
856
+ align-items: flex-end;
857
+ justify-content: center;
858
+ min-height: 90px;
859
+ bottom: 0;
860
+ box-sizing: border-box;
861
+ z-index: 100;
862
+ width: 100%;
863
+ left: 0;
864
+ transition-property: transform, max-width, min-width;
865
+ transition-duration: var(--cib-motion-duration-slowest);
866
+ transition-timing-function: var(--cib-motion-easing-transition);
867
+ }
868
+
869
+ .action-root {
870
+ position: relative;
871
+ display: flex;
872
+ align-items: flex-start;
873
+ gap: 12px;
874
+ width: calc(100% - var(--side-panel-width));
875
+ height: auto;
876
+ max-width: 1120px;
877
+ min-height: 90px;
878
+ transition-property: width, max-width;
879
+ transition-duration: var(--cib-motion-duration-slowest);
880
+ transition-timing-function: var(--cib-motion-easing-transition);
881
+ }
882
+
883
+ .root[visual-search] .main-container {
884
+ padding-inline-end: 84px;
885
+ }
886
+
887
+ .main-container {
888
+ display: flex;
889
+ flex-direction: column;
890
+ gap: 4px;
891
+ justify-content: space-between;
892
+ align-items: flex-start;
893
+ position: relative;
894
+ width: 100%;
895
+ height: 100%;
896
+ min-height: 48px;
897
+ overflow-y: auto;
898
+ box-sizing: border-box;
899
+ padding-block: 13px 11px;
900
+ padding-inline: 16px;
901
+ z-index: 1;
902
+ background: var(--cib-color-background-surface-solid-quaternary);
903
+ border-radius: var(--cib-action-bar-search-border-radius);
904
+ outline: transparent solid 1px;
905
+ cursor: text;
906
+ transition-property: min-height, height, width, transform, border-radius, box-shadow;
907
+ transition-duration: var(--cib-motion-duration-fast);
908
+ transition-timing-function: var(--cib-motion-easing-in);
909
+ transition-delay: var(--cib-motion-duration-faster);
910
+ box-shadow: var(--cib-shadow-card);
911
+
912
+ img {
913
+ cursor: pointer;
914
+ user-select: none;
915
+ }
916
+
917
+ textarea {
918
+ white-space: nowrap;
919
+ text-overflow: ellipsis;
920
+ overflow-x: hidden;
921
+ }
922
+
923
+ &:hover,
924
+ &.active {
925
+ min-height: 90px;
926
+ border-radius: var(--cib-border-radius-extra-large);
927
+
928
+ .bottom-bar {
929
+ opacity: 1;
930
+ }
931
+
932
+ textarea {
933
+ white-space: pre-wrap;
934
+ }
935
+ }
936
+
937
+ .main-bar {
938
+ display: flex;
939
+ flex-direction: row;
940
+ width: 100%;
941
+ gap: 16px;
942
+ justify-content: space-between;
943
+ align-items: flex-start;
944
+ &>*:nth-child(n+5) {
945
+ display: none;
946
+ }
947
+ }
948
+
949
+ .message-input {
950
+ max-height: 50vh;
951
+ overflow-y: auto;
952
+ }
953
+ }
954
+
955
+ .body-1 {
956
+ font-size: var(--cib-type-body1-font-size);
957
+ line-height: var(--cib-type-body1-line-height);
958
+ font-weight: var(--cib-type-body1-font-weight);
959
+ font-variation-settings: var(--cib-type-body1-font-variation-settings);
960
+ }
961
+
962
+ .body-2 {
963
+ font-size: var(--cib-type-body2-font-size);
964
+ line-height: var(--cib-type-body2-line-height);
965
+ font-weight: var(--cib-type-body2-font-weight);
966
+ font-variation-settings: var(--cib-type-body2-font-variation-settings);
967
+ }
968
+
969
+ .outside-left-container {
970
+ position: relative;
971
+ align-self: flex-end;
972
+ height: 48px;
973
+ bottom: 42px;
974
+ margin: 0px;
975
+ padding: 0px;
976
+ transition-property: opacity;
977
+ transition-duration: var(--cib-motion-duration-slow);
978
+ transition-delay: var(--cib-motion-duration-normal);
979
+ transition-timing-function: var(--cib-motion-easing-transition);
980
+
981
+ .button-compose-wrapper {
982
+ transition-property: opacity, transform;
983
+ transition-duration: var(--cib-motion-duration-fast);
984
+ transition-timing-function: var(--cib-motion-easing-in);
985
+ }
986
+
987
+ .button-compose {
988
+ display: flex;
989
+ flex-direction: row;
990
+ position: relative;
991
+ height: 48px;
992
+ width: var(--button-compose-expanded-width);
993
+ font-family: var(--cib-font-text);
994
+ border-radius: var(--cib-border-radius-circular);
995
+ color: var(--cib-color-foreground-on-accent-primary);
996
+ fill: var(--cib-color-foreground-on-accent-primary);
997
+ background: transparent;
998
+ border: none;
999
+ outline: transparent solid 1px;
1000
+ margin: 0px;
1001
+ padding: 0px;
1002
+ overflow: hidden;
1003
+ transition-property: width, opacity;
1004
+ transition-duration: var(--cib-motion-duration-normal);
1005
+ transition-timing-function: var(--cib-motion-easing-in);
1006
+
1007
+ &:not([disabled]) {
1008
+ pointer-events: auto;
1009
+ cursor: pointer;
1010
+ }
1011
+
1012
+ &::before {
1013
+ content: "";
1014
+ position: absolute;
1015
+ width: 100%;
1016
+ height: 100%;
1017
+ border-radius: var(--cib-border-radius-circular);
1018
+ background: var(--cib-color-fill-accent-gradient-primary);
1019
+ box-shadow: var(--cib-shadow-elevation-4);
1020
+ transition-property: transform;
1021
+ transition-duration: var(--cib-motion-duration-fast);
1022
+ transition-timing-function: var(--cib-motion-easing-in);
1023
+ }
1024
+ }
1025
+
1026
+ &.collapsed .button-compose {
1027
+ width: var(--button-compose-collapsed-width);
1028
+ }
1029
+
1030
+ &:hover .button-compose {
1031
+ width: var(--button-compose-expanded-width);
1032
+ }
1033
+ @media (max-width: 600px) {
1034
+ .button-compose {
1035
+ width: var(--button-compose-collapsed-width) !important;
1036
+ }
1037
+ }
1038
+
1039
+ .button-compose-content {
1040
+ position: relative;
1041
+ display: grid;
1042
+ grid-template-columns: 48px auto;
1043
+ align-items: center;
1044
+ height: 48px;
1045
+ }
1046
+
1047
+ .button-compose-text {
1048
+ min-width: max-content;
1049
+ margin-inline-end: 20px;
1050
+ transition-property: opacity;
1051
+ transition-duration: var(--cib-motion-duration-fast);
1052
+ transition-timing-function: var(--cib-motion-easing-in);
1053
+ }
1054
+
1055
+ }
1056
+
1057
+ .bottom-bar {
1058
+ position: absolute;
1059
+ display: flex;
1060
+ flex-direction: row;
1061
+ align-items: center;
1062
+ justify-content: space-between;
1063
+ height: 36px;
1064
+ bottom: 4px;
1065
+ inset-inline: 0px;
1066
+ box-sizing: border-box;
1067
+ padding-block: 0px;
1068
+ padding-inline: 16px 8px;
1069
+ opacity: 0;
1070
+ transition-property: opacity;
1071
+ transition-duration: var(--cib-motion-duration-faster);
1072
+ transition-delay: var(--cib-motion-duration-faster);
1073
+ transition-timing-function: var(--cib-motion-easing-transition);
1074
+
1075
+ .letter-counter {
1076
+ color: var(--cib-color-foreground-neutral-secondary);
1077
+ }
1078
+ }
1079
+
1080
+ .fade {
1081
+ position: fixed;
1082
+ left: 0;
1083
+ height: 104px;
1084
+ width: 100%;
1085
+ z-index: -1;
1086
+ overflow: hidden;
1087
+ clip-path: inset(0px);
1088
+ pointer-events: none;
1089
+
1090
+ &.bottom {
1091
+ display: block;
1092
+ bottom: 0px;
1093
+ height: 140px;
1094
+ -webkit-mask-image: linear-gradient(transparent calc(100% - 140px), black calc(100% - 118px));
1095
+ mask-image: linear-gradient(transparent calc(100% - 140px), black calc(100% - 118px));
1096
+ }
1097
+
1098
+ .background {
1099
+ height: 100%;
1100
+ transition-property: transform;
1101
+ transition-duration: var(--cib-motion-duration-slowest);
1102
+ transition-timing-function: var(--cib-motion-easing-transition);
1103
+ background: var(--cib-color-fill-accent-gradient-quaternary);
1104
+ }
1105
+ }
1106
+
1107
+ @media (max-width: 600px) {
1108
+ .action-root {
1109
+ align-items: flex-end;
1110
+ justify-content: flex-end;
1111
+ min-height: unset;
1112
+ }
1113
+
1114
+ .main-container {
1115
+ width: calc(100% - 60px);
1116
+ button[type="submit"] {
1117
+ display: none;
1118
+ }
1119
+ &:hover, &.active {
1120
+ width: 100%;
1121
+ transition-delay: 167ms;
1122
+ }
1123
+ }
1124
+
1125
+ .outside-left-container {
1126
+ position: absolute;
1127
+ bottom: 0px;
1128
+ inset-inline-start: 0px;
1129
+ }
1130
+ }
src/app/layout.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Metadata } from 'next'
2
+ import { Toaster } from 'react-hot-toast'
3
+ import { TailwindIndicator } from '@/components/tailwind-indicator'
4
+ import { Providers } from '@/components/providers'
5
+ import { Header } from '@/components/header'
6
+
7
+ import '@/app/globals.scss'
8
+
9
+
10
+ export const metadata: Metadata = {
11
+ title: {
12
+ default: 'Bing AI Chatbot',
13
+ template: `%s - Bing AI Chatbot`
14
+ },
15
+ description: 'Bing AI Chatbot Web App.',
16
+ themeColor: [
17
+ { media: '(prefers-color-scheme: light)', color: 'white' },
18
+ { media: '(prefers-color-scheme: dark)', color: 'dark' }
19
+ ],
20
+ icons: {
21
+ icon: '/favicon.ico',
22
+ shortcut: '../assets/images/logo.svg',
23
+ apple: '../assets/images/logo.svg'
24
+ }
25
+ }
26
+
27
+ interface RootLayoutProps {
28
+ children: React.ReactNode
29
+ }
30
+
31
+ export default function RootLayout({ children }: RootLayoutProps) {
32
+ return (
33
+ <html lang="zh-CN" suppressHydrationWarning>
34
+ <body>
35
+ <Toaster />
36
+ <Providers attribute="class" defaultTheme="system" enableSystem>
37
+ <div className="flex flex-col min-h-screen">
38
+ {/* @ts-ignore */}
39
+ <Header />
40
+ <main className="flex flex-col flex-1">{children}</main>
41
+ </div>
42
+ <TailwindIndicator />
43
+ </Providers>
44
+ </body>
45
+ </html>
46
+ )
47
+ }
src/app/loading.css ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ::-webkit-scrollbar {
2
+ width: 10px;
3
+ height: 10px;
4
+ display: none;
5
+ }
6
+
7
+ ::-webkit-scrollbar-button:start:decrement,
8
+ ::-webkit-scrollbar-button:end:increment {
9
+ height: 30px;
10
+ background-color: transparent;
11
+ }
12
+
13
+ ::-webkit-scrollbar-track-piece {
14
+ background-color: #3b3b3b;
15
+ -webkit-border-radius: 16px;
16
+ }
17
+
18
+ ::-webkit-scrollbar-thumb:vertical {
19
+ height: 50px;
20
+ background-color: #666;
21
+ border: 1px solid #eee;
22
+ -webkit-border-radius: 6px;
23
+ }
24
+
25
+ /* loading start */
26
+ .loading-spinner {
27
+ display: flex;
28
+ justify-content: center;
29
+ align-items: center;
30
+ height: 100vh;
31
+ opacity: 1;
32
+ transition: opacity .8s ease-out;
33
+ }
34
+
35
+ .loading-spinner.hidden {
36
+ opacity: 0;
37
+ }
38
+
39
+ .loading-spinner>div {
40
+ width: 30px;
41
+ height: 30px;
42
+ background: linear-gradient(90deg, #2870EA 10.79%, #1B4AEF 87.08%);
43
+
44
+ border-radius: 100%;
45
+ display: inline-block;
46
+ animation: sk-bouncedelay 1.4s infinite ease-in-out both;
47
+ }
48
+
49
+ .loading-spinner .bounce1 {
50
+ animation-delay: -0.32s;
51
+ }
52
+
53
+ .loading-spinner .bounce2 {
54
+ animation-delay: -0.16s;
55
+ }
56
+
57
+ @keyframes sk-bouncedelay {
58
+
59
+ 0%,
60
+ 80%,
61
+ 100% {
62
+ transform: scale(0);
63
+ }
64
+
65
+ 40% {
66
+ transform: scale(1.0);
67
+ }
68
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dynamic from 'next/dynamic'
2
+
3
+ const DynamicComponentWithNoSSR = dynamic(
4
+ () => import('../components/chat'),
5
+ { ssr: false }
6
+ )
7
+
8
+ export default function IndexPage() {
9
+ return (
10
+ <>
11
+ <div className="loading-spinner" />
12
+ <DynamicComponentWithNoSSR />
13
+ </>
14
+ )
15
+ }
src/assets/images/brush.svg ADDED
src/assets/images/chat.svg ADDED
src/assets/images/check-mark.svg ADDED
src/assets/images/help.svg ADDED
src/assets/images/logo.svg ADDED
src/assets/images/pin-fill.svg ADDED
src/assets/images/pin.svg ADDED
src/assets/images/send.svg ADDED
src/assets/images/settings.svg ADDED
src/assets/images/speech.svg ADDED
src/assets/images/stop.svg ADDED
src/assets/images/visual-search.svg ADDED
src/assets/images/voice.svg ADDED
src/assets/images/warning.svg ADDED
src/components/button-scroll-to-bottom.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+ import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7
+ import { Button, type ButtonProps } from '@/components/ui/button'
8
+ import { IconArrowDown } from '@/components/ui/icons'
9
+
10
+ export function ButtonScrollToBottom({ className, ...props }: ButtonProps) {
11
+ const isAtBottom = useAtBottom()
12
+
13
+ return (
14
+ <Button
15
+ variant="outline"
16
+ size="icon"
17
+ className={cn(
18
+ 'fixed right-4 bottom-24 z-50 bg-background transition-opacity duration-300 sm:right-20',
19
+ isAtBottom ? 'opacity-0' : 'opacity-100',
20
+ className
21
+ )}
22
+ onClick={() =>
23
+ window.scrollTo({
24
+ top: document.body.offsetHeight,
25
+ behavior: 'smooth'
26
+ })
27
+ }
28
+ {...props}
29
+ >
30
+ <IconArrowDown />
31
+ <span className="sr-only">Scroll to bottom</span>
32
+ </Button>
33
+ )
34
+ }
src/components/chat-header.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import LogoIcon from '@/assets/images/logo.svg'
2
+ import Image from 'next/image'
3
+
4
+ export function ChatHeader() {
5
+ return (
6
+ <div className="flex flex-col items-center justify-center">
7
+ <Image alt="logo" src={LogoIcon} width={60}/>
8
+ <div className="mt-8 text-4xl font-bold">欢迎使用新必应</div>
9
+ <div className="mt-4 mb-8 text-lg">由 AI 支持的网页版 Copilot</div>
10
+ </div>
11
+ )
12
+ }
src/components/chat-list.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+
3
+ import { Separator } from '@/components/ui/separator'
4
+ import { ChatMessage } from '@/components/chat-message'
5
+ import { ChatMessageModel } from '@/lib/bots/bing/types'
6
+
7
+ export interface ChatList {
8
+ messages: ChatMessageModel[]
9
+ }
10
+
11
+ export function ChatList({ messages }: ChatList) {
12
+ if (!messages.length) {
13
+ return null
14
+ }
15
+
16
+ return (
17
+ <div className="chat-container relative flex flex-col">
18
+ {messages.map((message, index) => (
19
+ <React.Fragment key={index}>
20
+ <ChatMessage message={message} />
21
+ {index < messages.length - 1 && (
22
+ <Separator className="my-2" />
23
+ )}
24
+ </React.Fragment>
25
+ ))}
26
+ </div>
27
+ )
28
+ }
src/components/chat-message.tsx ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import remarkGfm from 'remark-gfm'
2
+ import remarkMath from 'remark-math'
3
+ import supersub from 'remark-supersub'
4
+ import remarkBreaks from 'remark-breaks'
5
+ import { cn } from '@/lib/utils'
6
+ import { CodeBlock } from '@/components/ui/codeblock'
7
+ import { MemoizedReactMarkdown } from '@/components/markdown'
8
+ import { LearnMore } from './learn-more'
9
+ import { ChatMessageModel } from '@/lib/bots/bing/types'
10
+ import { useEffect } from 'react'
11
+ import { TurnCounter } from './turn-counter'
12
+
13
+ export interface ChatMessageProps {
14
+ message: ChatMessageModel
15
+ }
16
+
17
+ export function ChatMessage({ message, ...props }: ChatMessageProps) {
18
+ useEffect(() => {
19
+ if (document.body.scrollHeight - window.innerHeight - window.scrollY - 200 < 0) {
20
+ window.scrollBy(0, 200)
21
+ }
22
+ }, [message.text])
23
+
24
+ return message.text ? (
25
+ <div
26
+ className={cn('text-message', message.author)}
27
+ {...props}
28
+ >
29
+ <div className="text-message-content">
30
+ <MemoizedReactMarkdown
31
+ linkTarget="_blank"
32
+ className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0"
33
+ remarkPlugins={[remarkGfm, remarkMath, supersub, remarkBreaks]}
34
+ components={{
35
+ img(obj) {
36
+ try {
37
+ const uri = new URL(obj.src!)
38
+ const w = uri.searchParams.get('w')
39
+ const h = uri.searchParams.get('h')
40
+ if (w && h) {
41
+ uri.searchParams.delete('w')
42
+ uri.searchParams.delete('h')
43
+ return <a style={{ float: 'left', maxWidth: '50%' }} href={uri.toString()} target="_blank" rel="noopener noreferrer"><img src={obj.src} alt={obj.alt} width={w!} height={h!}/></a>
44
+ }
45
+ } catch (e) {
46
+ }
47
+ return <img src={obj.src} alt={obj.alt} title={obj.title} />
48
+ },
49
+ p({ children }) {
50
+ return <p className="mb-2">{children}</p>
51
+ },
52
+ code({ node, inline, className, children, ...props }) {
53
+ if (children.length) {
54
+ if (children[0] == '▍') {
55
+ return (
56
+ <span className="mt-1 animate-pulse cursor-default">▍</span>
57
+ )
58
+ }
59
+
60
+ children[0] = (children[0] as string).replace('`▍`', '▍')
61
+ }
62
+
63
+ const match = /language-(\w+)/.exec(className || '')
64
+
65
+ if (inline) {
66
+ return (
67
+ <code className={className} {...props}>
68
+ {children}
69
+ </code>
70
+ )
71
+ }
72
+
73
+ return (
74
+ <CodeBlock
75
+ key={Math.random()}
76
+ language={(match && match[1]) || ''}
77
+ value={String(children).replace(/\n$/, '')}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+ }}
83
+ >
84
+ {message.text}
85
+ </MemoizedReactMarkdown>
86
+ </div>
87
+ <div className="text-message-footer">
88
+ {message.author === 'bot' && <LearnMore sourceAttributions={message.sourceAttributions} />}
89
+ {message.author === 'bot' && <TurnCounter throttling={message.throttling} />}
90
+ </div>
91
+ </div>
92
+ ) : null
93
+ }
src/components/chat-notification.tsx ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react'
2
+ import Image from 'next/image'
3
+
4
+ import IconWarning from '@/assets/images/warning.svg'
5
+ import { ChatError, ErrorCode, ChatMessageModel } from '@/lib/bots/bing/types'
6
+ import { ExternalLink } from './external-link'
7
+ import { useBing } from '@/lib/hooks/use-bing'
8
+
9
+ export interface ChatNotificationProps extends Pick<ReturnType<typeof useBing>, 'bot'> {
10
+ message?: ChatMessageModel
11
+ }
12
+
13
+ function getAction(error: ChatError, reset: () => void) {
14
+ if (error.code === ErrorCode.THROTTLE_LIMIT) {
15
+ reset()
16
+ return (
17
+ <div>
18
+ 你已达到每日最大发送消息次数,请<a href={`#dialog="settings"`}>更换账号</a>或隔一天后重试
19
+ </div>
20
+ )
21
+ }
22
+ if (error.code === ErrorCode.BING_FORBIDDEN) {
23
+ return (
24
+ <ExternalLink href="https://bing.com/new">
25
+ 你的账号已在黑名单,请尝试更换账号及申请解封
26
+ </ExternalLink>
27
+ )
28
+ }
29
+ if (error.code === ErrorCode.CONVERSATION_LIMIT) {
30
+ return (
31
+ <div>
32
+ 当前话题已中止,请点
33
+ <a href={`#dialog="reset"`}>重新开始</a>
34
+ 开启新的对话
35
+ </div>
36
+ )
37
+ }
38
+ if (error.code === ErrorCode.BING_CAPTCHA) {
39
+ return (
40
+ <ExternalLink href="https://www.bing.com/turing/captcha/challenge">
41
+ 点击通过人机验证
42
+ </ExternalLink>
43
+ )
44
+ }
45
+ if (error.code === ErrorCode.BING_UNAUTHORIZED) {
46
+ reset()
47
+ return (
48
+ <a href={`#dialog="settings"`}>没有获取到身份信息或身份信息失效,点此重新设置</a>
49
+ )
50
+ }
51
+ return error.message
52
+ }
53
+
54
+ export function ChatNotification({ message, bot }: ChatNotificationProps) {
55
+ useEffect(() => {
56
+ window.scrollBy(0, 2000)
57
+ }, [message])
58
+
59
+ if (!message?.error) return
60
+
61
+ return (
62
+ <div
63
+ className="notification-container"
64
+ >
65
+ <div className="bottom-notifications">
66
+ <div className="inline-type with-decorative-line">
67
+ <div className="text-container mt-1">
68
+ <div className="title inline-flex items-start">
69
+ <Image alt="error" src={IconWarning} width={20} className="mr-1 mt-1" />
70
+ {getAction(message.error, () => bot.resetConversation())}
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ )
77
+ }
src/components/chat-panel.tsx ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import Image from 'next/image'
5
+ import Textarea from 'react-textarea-autosize'
6
+ import { useAtomValue } from 'jotai'
7
+ import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
8
+ import { cn } from '@/lib/utils'
9
+
10
+ import BrushIcon from '@/assets/images/brush.svg'
11
+ import ChatIcon from '@/assets/images/chat.svg'
12
+ import VisualSearchIcon from '@/assets/images/visual-search.svg'
13
+ import SendIcon from '@/assets/images/send.svg'
14
+ import PinIcon from '@/assets/images/pin.svg'
15
+ import PinFillIcon from '@/assets/images/pin-fill.svg'
16
+
17
+ import { useBing } from '@/lib/hooks/use-bing'
18
+ import { voiceListenAtom } from '@/state'
19
+ import Voice from './voice'
20
+
21
+ export interface ChatPanelProps
22
+ extends Pick<
23
+ ReturnType<typeof useBing>,
24
+ | 'generating'
25
+ | 'input'
26
+ | 'setInput'
27
+ | 'sendMessage'
28
+ | 'resetConversation'
29
+ | 'isSpeaking'
30
+ > {
31
+ id?: string
32
+ className?: string
33
+ }
34
+
35
+ export function ChatPanel({
36
+ isSpeaking,
37
+ generating,
38
+ input,
39
+ setInput,
40
+ className,
41
+ sendMessage,
42
+ resetConversation
43
+ }: ChatPanelProps) {
44
+ const inputRef = React.useRef<HTMLTextAreaElement>(null)
45
+ const {formRef, onKeyDown} = useEnterSubmit()
46
+ const [focused, setFocused] = React.useState(false)
47
+ const [active, setActive] = React.useState(false)
48
+ const [pin, setPin] = React.useState(false)
49
+ const [tid, setTid] = React.useState<any>()
50
+ const voiceListening = useAtomValue(voiceListenAtom)
51
+
52
+ const setBlur = React.useCallback(() => {
53
+ clearTimeout(tid)
54
+ setActive(false)
55
+ const _tid = setTimeout(() => setFocused(false), 2000);
56
+ setTid(_tid)
57
+ }, [tid])
58
+
59
+ const setFocus = React.useCallback(() => {
60
+ setFocused(true)
61
+ setActive(true)
62
+ clearTimeout(tid)
63
+ inputRef.current?.focus()
64
+ }, [tid])
65
+
66
+ React.useEffect(() => {
67
+ if (input) {
68
+ setFocus()
69
+ }
70
+ }, [input])
71
+
72
+ return (
73
+ <form
74
+ className={cn('chat-panel', className)}
75
+ onSubmit={async e => {
76
+ e.preventDefault()
77
+ if (generating) {
78
+ return;
79
+ }
80
+ if (!input?.trim()) {
81
+ return
82
+ }
83
+ setInput('')
84
+ setPin(false)
85
+ await sendMessage(input)
86
+ }}
87
+ ref={formRef}
88
+ >
89
+ <div className="action-bar pb-4">
90
+ <div className={cn('action-root', { focus: active || pin })} speech-state="hidden" visual-search="" drop-target="">
91
+ <div className="fade bottom">
92
+ <div className="background"></div>
93
+ </div>
94
+ <div className={cn('outside-left-container', { collapsed: focused })}>
95
+ <div className="button-compose-wrapper">
96
+ <button className="body-2 button-compose" type="button" aria-label="新主题" onClick={resetConversation}>
97
+ <div className="button-compose-content">
98
+ <Image className="pl-2" alt="brush" src={BrushIcon} width={40} />
99
+ <div className="button-compose-text">新主题</div>
100
+ </div>
101
+ </button>
102
+ </div>
103
+ </div>
104
+ <div
105
+ className={cn('main-container', { active: active || pin })}
106
+ style={{ minHeight: pin ? '360px' : undefined }}
107
+ onClickCapture={setFocus}
108
+ onBlurCapture={setBlur}
109
+ >
110
+ <div className="main-bar">
111
+ <Image alt="chat" src={ChatIcon} width={20} color="blue" />
112
+ <Textarea
113
+ ref={inputRef}
114
+ tabIndex={0}
115
+ onKeyDown={onKeyDown}
116
+ rows={1}
117
+ value={input}
118
+ onChange={e => setInput(e.target.value.slice(0, 4000))}
119
+ placeholder={voiceListening ? '持续对话中...对话完成说“发送”即可' : 'Shift + Enter 换行'}
120
+ spellCheck={false}
121
+ className="message-input min-h-[24px] -mx-1 w-full text-base resize-none bg-transparent focus-within:outline-none"
122
+ />
123
+ <Image alt="visual-search" src={VisualSearchIcon} width={20} />
124
+ <Voice setInput={setInput} sendMessage={sendMessage} isSpeaking={isSpeaking} input={input} />
125
+ <button type="submit">
126
+ <Image alt="send" src={SendIcon} width={20} style={{ marginTop: '2px' }} />
127
+ </button>
128
+ </div>
129
+ <div className="body-1 bottom-bar">
130
+ <div className="letter-counter"><span>{input.length}</span>/4000</div>
131
+ <button onClick={() => {
132
+ setPin(!pin)
133
+ }} className="pr-2">
134
+ <Image alt="pin" src={pin ? PinFillIcon : PinIcon} width={20} />
135
+ </button>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </form>
141
+ )
142
+ }
src/components/chat-scroll-anchor.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { useInView } from 'react-intersection-observer'
5
+
6
+ import { useAtBottom } from '@/lib/hooks/use-at-bottom'
7
+
8
+ interface ChatScrollAnchorProps {
9
+ trackVisibility?: boolean
10
+ }
11
+
12
+ export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
13
+ const isAtBottom = useAtBottom()
14
+ const { ref, entry, inView } = useInView({
15
+ trackVisibility,
16
+ delay: 100,
17
+ rootMargin: '0px 0px -150px 0px'
18
+ })
19
+
20
+ React.useEffect(() => {
21
+ if (isAtBottom && trackVisibility && !inView) {
22
+ entry?.target.scrollIntoView({
23
+ block: 'start'
24
+ })
25
+ }
26
+ }, [inView, entry, isAtBottom, trackVisibility])
27
+
28
+ return <div ref={ref} className="h-px w-full" />
29
+ }
src/components/chat-suggestions.tsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useMemo } from 'react'
2
+ import Image from 'next/image'
3
+ import HelpIcon from '@/assets/images/help.svg'
4
+ import { SuggestedResponse } from '@/lib/bots/bing/types'
5
+ import { useBing } from '@/lib/hooks/use-bing'
6
+ import { atom, useAtom } from 'jotai'
7
+
8
+ type Suggestions = SuggestedResponse[]
9
+ const helpSuggestions = ['为什么不回应某些主题', '告诉我更多关于必应的资迅', '必应如何使用 AI?'].map((text) => ({ text }))
10
+ const suggestionsAtom = atom<Suggestions>([])
11
+
12
+ type ChatSuggestionsProps = React.ComponentProps<'div'> & Pick<ReturnType<typeof useBing>, 'setInput'> & { suggestions?: Suggestions }
13
+
14
+ export function ChatSuggestions({ setInput, suggestions = [] }: ChatSuggestionsProps) {
15
+ const [currentSuggestions, setSuggestions] = useAtom(suggestionsAtom)
16
+ const toggleSuggestions = (() => {
17
+ if (currentSuggestions === helpSuggestions) {
18
+ setSuggestions(suggestions)
19
+ } else {
20
+ setSuggestions(helpSuggestions)
21
+ }
22
+ })
23
+
24
+ useMemo(() => {
25
+ setSuggestions(suggestions)
26
+ window.scrollBy(0, 2000)
27
+ }, [suggestions.length])
28
+
29
+ return currentSuggestions?.length ? (
30
+ <div className="py-6">
31
+ <div className="suggestion-items">
32
+ <button className="rai-button" type="button" aria-label="这是什么?" onClick={toggleSuggestions}>
33
+ <Image alt="help" src={HelpIcon} width={24} />
34
+ </button>
35
+ {
36
+ currentSuggestions.map(suggestion => (
37
+ <button key={suggestion.text} className="body-1-strong suggestion-container" type="button" onClick={() => setInput(suggestion.text)}>
38
+ {suggestion.text}
39
+ </button>
40
+ ))
41
+ }
42
+ </div>
43
+ </div>
44
+ ) : null
45
+ }
src/components/chat.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { useAtom } from 'jotai'
5
+ import Image from 'next/image'
6
+ import { cn } from '@/lib/utils'
7
+ import { ChatList } from '@/components/chat-list'
8
+ import { ChatPanel } from '@/components/chat-panel'
9
+ import { WelcomeScreen } from '@/components/welcome-screen'
10
+ import { ChatScrollAnchor } from '@/components/chat-scroll-anchor'
11
+ import { ToneSelector } from './tone-selector'
12
+ import { ChatHeader } from './chat-header'
13
+ import { ChatSuggestions } from './chat-suggestions'
14
+ import { bingConversationStyleAtom } from '@/state'
15
+ import { ButtonScrollToBottom } from '@/components/button-scroll-to-bottom'
16
+ import StopIcon from '@/assets/images/stop.svg'
17
+ import { useBing } from '@/lib/hooks/use-bing'
18
+ import { ChatMessageModel } from '@/lib/bots/bing/types'
19
+ import { ChatNotification } from './chat-notification'
20
+ import { Settings } from './settings'
21
+
22
+ export type ChatProps = React.ComponentProps<'div'> & { initialMessages?: ChatMessageModel[] }
23
+
24
+ export default function Chat({ className }: ChatProps) {
25
+
26
+ const [bingStyle, setBingStyle] = useAtom(bingConversationStyleAtom)
27
+ const { messages, sendMessage, resetConversation, stopGenerating, setInput, bot,input, generating, isSpeaking } = useBing()
28
+
29
+ useEffect(() => {
30
+ window.scrollTo({
31
+ top: document.body.offsetHeight,
32
+ behavior: 'smooth'
33
+ })
34
+ }, [])
35
+
36
+ return (
37
+ <div className="flex flex-1 flex-col">
38
+ <Settings />
39
+ <div className={cn('flex-1 pb-16', className)}>
40
+ <ChatHeader />
41
+ <WelcomeScreen setInput={setInput} />
42
+ <ToneSelector type={bingStyle} onChange={setBingStyle} />
43
+ {messages.length ? (
44
+ <>
45
+ <ChatList messages={messages} />
46
+ <ChatScrollAnchor trackVisibility={generating} />
47
+ <ChatNotification message={messages.at(-1)} bot={bot} />
48
+ <ChatSuggestions setInput={setInput} suggestions={messages.at(-1)?.suggestedResponses} />
49
+
50
+ {generating ? (
51
+ <div className="flex h-10 items-center justify-center my-4">
52
+ <button
53
+ onClick={stopGenerating}
54
+ className="typing-control-item stop"
55
+ >
56
+ <Image alt="stop" src={StopIcon} width={24} className="mr-1" />
57
+ <span>停止响应</span>
58
+ </button>
59
+ </div>
60
+ ) : null}
61
+ </>
62
+ ) : null}
63
+ </div>
64
+ <ChatPanel
65
+ className="pt-24 z-10"
66
+ isSpeaking={isSpeaking}
67
+ generating={generating}
68
+ sendMessage={sendMessage}
69
+ input={input}
70
+ setInput={setInput}
71
+ resetConversation={resetConversation}
72
+ />
73
+ <ButtonScrollToBottom />
74
+ </div>
75
+ )
76
+ }
src/components/external-link.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function ExternalLink({
2
+ href,
3
+ children
4
+ }: {
5
+ href: string
6
+ children: React.ReactNode
7
+ }) {
8
+ return (
9
+ <a
10
+ href={href}
11
+ target="_blank"
12
+ rel="noreferrer"
13
+ className="inline-flex flex-1 justify-center gap-1 underline"
14
+ >
15
+ <span>{children}</span>
16
+ <svg
17
+ aria-hidden="true"
18
+ height="7"
19
+ viewBox="0 0 6 6"
20
+ width="7"
21
+ className="opacity-70"
22
+ >
23
+ <path
24
+ d="M1.25215 5.54731L0.622742 4.9179L3.78169 1.75597H1.3834L1.38936 0.890915H5.27615V4.78069H4.40513L4.41109 2.38538L1.25215 5.54731Z"
25
+ fill="currentColor"
26
+ ></path>
27
+ </svg>
28
+ </a>
29
+ )
30
+ }
src/components/header.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import { UserMenu } from './user-menu'
3
+
4
+ export async function Header() {
5
+ return (
6
+ <header className="sticky top-0 z-50 flex items-center justify-between w-full h-16 px-4 border-b shrink-0 bg-gradient-to-b from-background/10 via-background/50 to-background/80 backdrop-blur-xl">
7
+ <div className="flex items-center justify-end space-x-2 w-full">
8
+ <UserMenu />
9
+ </div>
10
+ </header>
11
+ )
12
+ }
src/components/learn-more.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import { SourceAttribution } from '@/lib/bots/bing/types'
3
+
4
+ export interface LearnMoreProps {
5
+ sourceAttributions?: SourceAttribution[]
6
+ }
7
+
8
+ export function LearnMore({ sourceAttributions }: LearnMoreProps) {
9
+ if (!sourceAttributions?.length) {
10
+ return null
11
+ }
12
+
13
+ return (
14
+ <div className="learn-more-root" role="list" aria-label="了解详细信息:">
15
+ <div className="learn-more">了解详细信息:</div>
16
+ <div className="attribution-container">
17
+ <div className="attribution-items">
18
+ {sourceAttributions.map((attribution, index) => {
19
+ const { providerDisplayName, seeMoreUrl } = attribution
20
+ const { host } = new URL(seeMoreUrl)
21
+ return (
22
+ <a
23
+ key={index}
24
+ className="attribution-item"
25
+ target="_blank"
26
+ role="listitem"
27
+ href={seeMoreUrl}
28
+ title={providerDisplayName}
29
+ tabIndex={index}
30
+ >
31
+ {index + 1}. {host}
32
+ </a>
33
+ )
34
+ })}
35
+ </div>
36
+ </div>
37
+ </div>
38
+ )
39
+ }
src/components/markdown.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { FC, memo } from 'react'
2
+ import ReactMarkdown, { Options } from 'react-markdown'
3
+
4
+ export const MemoizedReactMarkdown: FC<Options> = memo(
5
+ ReactMarkdown,
6
+ (prevProps, nextProps) =>
7
+ prevProps.children === nextProps.children &&
8
+ prevProps.className === nextProps.className
9
+ )
src/components/providers.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { ThemeProvider as NextThemesProvider } from 'next-themes'
5
+ import { ThemeProviderProps } from 'next-themes/dist/types'
6
+
7
+ import { TooltipProvider } from '@/components/ui/tooltip'
8
+
9
+ export function Providers({ children, ...props }: ThemeProviderProps) {
10
+ return (
11
+ <NextThemesProvider {...props}>
12
+ <TooltipProvider>{children}</TooltipProvider>
13
+ </NextThemesProvider>
14
+ )
15
+ }