feat: 可选聊天服务器、cloudflare 聊天服务器部署

pull/234/head v1.8.0
adams549659584 2023-05-25 18:09:32 +08:00
parent 842e02b9d7
commit bfd58c5a52
35 changed files with 2057 additions and 1308 deletions

1
.vscode/launch.json vendored
View File

@ -18,6 +18,7 @@
// "Go_Proxy_BingAI_SOCKS_PWD": "xxx",
// "Go_Proxy_BingAI_USER_TOKEN_1": "xxx",
// "Go_Proxy_BingAI_USER_TOKEN_2": "xxx"
// "Go_Proxy_BingAI_AUTH_KEY": "xxx",
}
}
]

View File

@ -2,6 +2,12 @@
基于微软 New Bing 简单定制,拥有一致的 UI 体验,支持 ChatGPT 提示词,国内可用,基本兼容微软 Bing AI 所有功能,无需登录即可畅聊。
⭐ Bing 官方聊天服务器(相对较快和稳定,推荐)不可用时, 可用 ModHeader 添加 X-Forwarded-For 请求头,对应 URL 是 wss://sydney.bing.com/sydney/ChatHub具体可参考 https://zhuanlan.zhihu.com/p/606655303
⭐ 聊天服务器 (暂时默认 Cloudflare ) 可在右上角 设置 => 服务选择 中切换
⭐ 自定义聊天服务器参考下面 [部署聊天服务器](#部署聊天服务器) 章节
⭐ 国内可用 (部署服务器需要直连 www.bing.com 不重定向 CN ,可配置 socks 连接)
⭐ 支持现有开源提示词库
@ -22,6 +28,7 @@
- [Railway](#Railway)
- [Vercel](#Vercel)
- [Render](#Render)
- [部署聊天服务器](#部署聊天服务器)
- [TODO](#TODO)
## 网页展示
@ -104,6 +111,8 @@ Go_Proxy_BingAI_SOCKS_PWD=xxx
Go_Proxy_BingAI_USER_TOKEN_1=xxx
Go_Proxy_BingAI_USER_TOKEN_2=xxx
Go_Proxy_BingAI_USER_TOKEN_3=xxx ...
# 简单授权认证密码,可选
Go_Proxy_BingAI_AUTH_KEY=xxx
```
## 部署
@ -153,7 +162,7 @@ services:
### Release
在 [Github Releases](https://github.com/adams549659584/go-proxy-bingai/releases) 下载适用于对应平台的压缩包,解压后可得到可执行文件 go-proxy-bingai直接运行即可。
在 [GitHub Releases](https://github.com/adams549659584/go-proxy-bingai/releases) 下载适用于对应平台的压缩包,解压后可得到可执行文件 go-proxy-bingai直接运行即可。
### Railway
@ -176,6 +185,8 @@ RAILWAY_DOCKERFILE_PATH=docker/Dockerfile
### Vercel
> ⭐ Vercel 部署不支持 Websocket ,需选择 官方聊天服务器 或 Cloudflare
一键部署,点这里 => [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/adams549659584/go-proxy-bingai&project-name=go-proxy-bingai&repository-name=go-proxy-bingai-vercel)
![Vercel 一键部署](./docs/img/vercel-1.png)
@ -190,11 +201,23 @@ RAILWAY_DOCKERFILE_PATH=docker/Dockerfile
![Render 域名](./docs/img/render-2.png)
## 部署聊天服务器
> 核心代码 [worker.js](./cloudflare/worker.js)
> 具体部署 Cloudflare Workers 教程自行查询,大概如下
- [注册 Cloudflare 账号](https://dash.cloudflare.com/sign-up)
- 创建 Worker 服务,复制 [worker.js](./cloudflare/worker.js) 全部代码,粘贴至创建的服务中,保存并部署。
- 触发器 中自定义访问域名。
## TODO
- [x] 撰写
- [x] Vue3 重构
- [x] 提示词
- [x] 历史聊天
- [ ] 导出消息到本地Markdown、图片、PDF
- [ ] 简单访问权限控制
- [x] 导出消息到本地Markdown、图片、PDF
- [x] 简单访问权限控制

View File

@ -1,10 +0,0 @@
package api
import (
"adams549659584/go-proxy-bingai/common"
"net/http"
)
func ChatHub(w http.ResponseWriter, r *http.Request) {
common.NewSingleHostReverseProxy(common.BING_CHAT_URL).ServeHTTP(w, r)
}

View File

@ -0,0 +1,48 @@
package helper
import (
"adams549659584/go-proxy-bingai/common"
"encoding/json"
"net/http"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func CommonResult(w http.ResponseWriter, code int, msg string, data interface{}) error {
res := Response{
Code: code,
Message: msg,
Data: data,
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(res)
if err != nil {
return err
}
return nil
}
func SuccessResult(w http.ResponseWriter, data interface{}) error {
return CommonResult(w, http.StatusOK, "success", data)
}
func ErrorResult(w http.ResponseWriter, code int, msg string) error {
return CommonResult(w, code, msg, nil)
}
func UnauthorizedResult(w http.ResponseWriter) error {
return ErrorResult(w, http.StatusUnauthorized, "unauthorized")
}
func CheckAuth(r *http.Request) bool {
isAuth := true
if len(common.AUTH_KEY) > 0 {
ckAuthKey, _ := r.Cookie(common.AUTH_KEY_COOKIE_NAME)
isAuth = ckAuthKey != nil && len(ckAuthKey.Value) > 0 && common.AUTH_KEY == ckAuthKey.Value
}
return isAuth
}

View File

@ -1,6 +1,7 @@
package api
import (
"adams549659584/go-proxy-bingai/api/helper"
"adams549659584/go-proxy-bingai/common"
"net/http"
)
@ -9,6 +10,10 @@ func Index(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.Redirect(w, r, common.PROXY_WEB_PAGE_PATH, http.StatusFound)
} else {
if !helper.CheckAuth(r) {
helper.UnauthorizedResult(w)
return
}
common.NewSingleHostReverseProxy(common.BING_URL).ServeHTTP(w, r)
}
}

15
api/sydney.go 100644
View File

@ -0,0 +1,15 @@
package api
import (
"adams549659584/go-proxy-bingai/api/helper"
"adams549659584/go-proxy-bingai/common"
"net/http"
)
func Sydney(w http.ResponseWriter, r *http.Request) {
if !helper.CheckAuth(r) {
helper.UnauthorizedResult(w)
return
}
common.NewSingleHostReverseProxy(common.BING_SYDNEY_URL).ServeHTTP(w, r)
}

24
api/sysConfig.go 100644
View File

@ -0,0 +1,24 @@
package api
import (
"adams549659584/go-proxy-bingai/api/helper"
"adams549659584/go-proxy-bingai/common"
"net/http"
)
type SysConfig struct {
// 是否系统配置 cookie
IsSysCK bool `json:"isSysCK"`
// 是否已授权
IsAuth bool `json:"isAuth"`
SydneyBaseUrl string `json:"sydneyBaseUrl"`
}
func SysConf(w http.ResponseWriter, r *http.Request) {
isAuth := helper.CheckAuth(r)
conf := SysConfig{
IsSysCK: len(common.USER_TOKEN_LIST) > 0,
IsAuth: isAuth,
}
helper.SuccessResult(w, conf)
}

View File

@ -1,6 +1,7 @@
package api
import (
"adams549659584/go-proxy-bingai/api/helper"
"adams549659584/go-proxy-bingai/common"
"adams549659584/go-proxy-bingai/web"
"net/http"
@ -10,6 +11,10 @@ func WebStatic(w http.ResponseWriter, r *http.Request) {
if _, ok := web.WEB_PATH_MAP[r.URL.Path]; ok || r.URL.Path == common.PROXY_WEB_PREFIX_PATH {
http.StripPrefix(common.PROXY_WEB_PREFIX_PATH, http.FileServer(web.GetWebFS())).ServeHTTP(w, r)
} else {
if !helper.CheckAuth(r) {
helper.UnauthorizedResult(w)
return
}
common.NewSingleHostReverseProxy(common.BING_URL).ServeHTTP(w, r)
}
}

View File

@ -0,0 +1,124 @@
const SYDNEY_ORIGIN = 'https://sydney.bing.com';
const KEEP_REQ_HEADERS = [
'accept',
'accept-encoding',
'accept-language',
'connection',
'cookie',
'upgrade',
'user-agent',
'sec-websocket-extensions',
'sec-websocket-key',
'sec-websocket-version',
'x-request-id',
'content-length',
'content-type',
'access-control-request-headers',
'access-control-request-method',
];
const IP_RANGE = [
['3.2.50.0', '3.5.31.255'], //192,000
['3.12.0.0', '3.23.255.255'], //786,432
['3.30.0.0', '3.33.34.255'], //205,568
['3.40.0.0', '3.63.255.255'], //1,572,864
['3.80.0.0', '3.95.255.255'], //1,048,576
['3.100.0.0', '3.103.255.255'], //262,144
['3.116.0.0', '3.119.255.255'], //262,144
['3.128.0.0', '3.247.255.255'], //7,864,320
];
/**
* 随机整数 [min,max)
* @param {number} min
* @param {number} max
* @returns
*/
const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min)) + min;
/**
* ip int
* @param {string} ip
* @returns
*/
const ipToInt = (ip) => {
const ipArr = ip.split('.');
let result = 0;
result += +ipArr[0] << 24;
result += +ipArr[1] << 16;
result += +ipArr[2] << 8;
result += +ipArr[3];
return result;
};
/**
* int ip
* @param {number} intIP
* @returns
*/
const intToIp = (intIP) => {
return `${(intIP >> 24) & 255}.${(intIP >> 16) & 255}.${(intIP >> 8) & 255}.${intIP & 255}`;
};
const getRandomIP = () => {
const randIndex = getRandomInt(0, IP_RANGE.length);
const startIp = IP_RANGE[randIndex][0];
const endIp = IP_RANGE[randIndex][1];
const startIPInt = ipToInt(startIp);
const endIPInt = ipToInt(endIp);
const randomInt = getRandomInt(startIPInt, endIPInt);
const randomIP = intToIp(randomInt);
return randomIP;
};
export default {
/**
* fetch
* @param {Request} request
* @param {*} env
* @param {*} ctx
* @returns
*/
async fetch(request, env, ctx) {
const currentUrl = new URL(request.url);
const targetUrl = new URL(SYDNEY_ORIGIN + currentUrl.pathname + currentUrl.search);
const newHeaders = new Headers();
request.headers.forEach((value, key) => {
// console.log(`old : ${key} : ${value}`);
if (KEEP_REQ_HEADERS.includes(key)) {
newHeaders.set(key, value);
}
});
newHeaders.set('host', targetUrl.host);
newHeaders.set('origin', targetUrl.origin);
newHeaders.set('referer', 'https://www.bing.com/search?q=Bing+AI');
const randIP = getRandomIP();
// console.log('randIP : ', randIP);
newHeaders.set('X-Forwarded-For', randIP);
const oldUA = request.headers.get('user-agent');
const isMobile = oldUA.includes('Mobile') || oldUA.includes('Android');
if (isMobile) {
newHeaders.set(
'user-agent',
'Mozilla/5.0 (iPhone; CPU iPhone OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.7 Mobile/15E148 Safari/605.1.15 BingSapphire/1.0.410427012'
);
} else {
newHeaders.set('user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35');
}
// newHeaders.forEach((value, key) => console.log(`${key} : ${value}`));
const newReq = new Request(targetUrl, {
method: request.method,
headers: newHeaders,
body: request.body,
});
// console.log('request url : ', newReq.url);
const res = await fetch(newReq);
return res;
// const newRes = new Response(res.body, res);
// newRes.headers.set('access-control-allow-origin', '*');
// newRes.headers.set('access-control-allow-methods', '*');
// newRes.headers.set('access-control-allow-headers', '*');
// return newRes;
},
};

View File

@ -2322,9 +2322,6 @@ var IP_RANGE = [][]string{
{"100.43.0.0", "100.43.95.255"}, //24,576
{"100.43.128.0", "100.63.255.255"}, //1,343,488
{"100.128.0.0", "100.255.255.255"}, //8,388,608
{"101.45.0.0", "101.45.255.255"}, //65,536
{"101.49.0.0", "101.49.127.255"}, //32,768
{"101.49.192.0", "101.49.255.255"}, //16,384
{"104.0.0.0", "104.28.7.255"}, //1,837,056
{"104.28.157.0", "104.28.255.255"}, //25,344
{"104.29.105.0", "104.30.1.255"}, //39,168
@ -2926,7 +2923,6 @@ var IP_RANGE = [][]string{
{"137.167.0.0", "137.169.40.255"}, //141,568
{"137.169.43.0", "137.170.255.255"}, //120,064
{"137.173.0.0", "137.173.255.255"}, //65,536
{"137.175.0.0", "137.175.127.255"}, //32,768
{"137.176.0.0", "137.177.34.255"}, //74,496
{"137.177.36.0", "137.184.159.255"}, //490,496
{"137.184.176.0", "137.184.247.255"}, //18,432

View File

@ -20,24 +20,29 @@ import (
)
var (
BING_CHAT_DOMAIN = "https://sydney.bing.com"
BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub")
BING_URL, _ = url.Parse("https://www.bing.com")
BING_SYDNEY_DOMAIN = "https://sydney.bing.com"
// BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub")
BING_SYDNEY_URL, _ = url.Parse(BING_SYDNEY_DOMAIN)
BING_URL, _ = url.Parse("https://www.bing.com")
// EDGE_SVC_URL, _ = url.Parse("https://edgeservices.bing.com")
KEEP_REQ_HEADER_MAP = map[string]bool{
"Accept": true,
"Accept-Encoding": true,
"Accept-Language": true,
"Referer": true,
"Connection": true,
"Cookie": true,
"Upgrade": true,
"User-Agent": true,
"Sec-Websocket-Extensions": true,
"Sec-Websocket-Key": true,
"Sec-Websocket-Version": true,
"X-Request-Id": true,
"X-Forwarded-For": true,
"Accept": true,
"Accept-Encoding": true,
"Accept-Language": true,
"Referer": true,
"Connection": true,
"Cookie": true,
"Upgrade": true,
"User-Agent": true,
"Sec-Websocket-Extensions": true,
"Sec-Websocket-Key": true,
"Sec-Websocket-Version": true,
"X-Request-Id": true,
"X-Forwarded-For": true,
"Content-Length": true,
"Content-Type": true,
"Access-Control-Request-Headers": true,
"Access-Control-Request-Method": true,
}
DEL_LOCATION_DOMAINS = []string{
"https://cn.bing.com",
@ -50,12 +55,29 @@ var (
USER_TOKEN_ENV_NAME_PREFIX = "Go_Proxy_BingAI_USER_TOKEN"
USER_TOKEN_LIST []string
RAND_COOKIE_INDEX_NAME = "BingAI_Rand_CK"
// socks
SOCKS_URL string
SOCKS_USER string
SOCKS_PWD string
// 访问权限密钥,可选
AUTH_KEY string
AUTH_KEY_COOKIE_NAME = "BingAI_Auth_Key"
)
func init() {
initEnv()
initUserToken()
}
func initEnv() {
// socks
SOCKS_URL = os.Getenv("Go_Proxy_BingAI_SOCKS_URL")
SOCKS_USER = os.Getenv("Go_Proxy_BingAI_SOCKS_USER")
SOCKS_PWD = os.Getenv("Go_Proxy_BingAI_SOCKS_PWD")
// auth
AUTH_KEY = os.Getenv("Go_Proxy_BingAI_AUTH_KEY")
}
func initUserToken() {
for _, env := range os.Environ() {
if strings.HasPrefix(env, USER_TOKEN_ENV_NAME_PREFIX) {
@ -70,7 +92,7 @@ func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
httpsSchemeName := "https"
var originalHost string
var originalPath string
var originalDomain string
// var originalDomain string
var randIP string
var resCKRandIndex string
director := func(req *http.Request) {
@ -79,18 +101,18 @@ func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
}
originalHost = req.Host
originalPath = req.URL.Path
originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost)
// originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost)
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
originalRefer := req.Referer()
if originalRefer != "" && !strings.Contains(originalRefer, PROXY_WEB_PAGE_PATH) {
req.Header.Set("Referer", strings.ReplaceAll(originalRefer, originalDomain, BING_URL.String()))
} else {
req.Header.Set("Referer", fmt.Sprintf("%s/search?q=Bing+AI", BING_URL.String()))
}
// originalRefer := req.Referer()
// if originalRefer != "" && !strings.Contains(originalRefer, originalDomain) {
// req.Header.Set("Referer", strings.ReplaceAll(originalRefer, originalDomain, BING_URL.String()))
// } else {
req.Header.Set("Referer", fmt.Sprintf("%s/search?q=Bing+AI", BING_URL.String()))
// }
// 同一会话尽量保持相同的随机IP
ckRandIP, _ := req.Cookie(RAND_IP_COOKIE_NAME)
@ -199,9 +221,9 @@ func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
}
// 跨域
res.Header.Set("Access-Control-Allow-Origin", "*")
res.Header.Set("Access-Control-Allow-Methods", "*")
res.Header.Set("Access-Control-Allow-Headers", "*")
// res.Header.Set("Access-Control-Allow-Origin", "*")
// res.Header.Set("Access-Control-Allow-Methods", "*")
// res.Header.Set("Access-Control-Allow-Headers", "*")
return nil
}
@ -226,18 +248,15 @@ func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
}
// socks
socksUrl := os.Getenv("Go_Proxy_BingAI_SOCKS_URL")
if socksUrl != "" {
socksUser := os.Getenv("Go_Proxy_BingAI_SOCKS_USER")
socksPwd := os.Getenv("Go_Proxy_BingAI_SOCKS_PWD")
if SOCKS_URL != "" {
var socksAuth *proxy.Auth
if socksUser != "" && socksPwd != "" {
if SOCKS_USER != "" && SOCKS_PWD != "" {
socksAuth = &proxy.Auth{
User: socksUser,
Password: socksPwd,
User: SOCKS_USER,
Password: SOCKS_PWD,
}
}
s5Proxy, err := proxy.SOCKS5("tcp", socksUrl, socksAuth, proxy.Direct)
s5Proxy, err := proxy.SOCKS5("tcp", SOCKS_URL, socksAuth, proxy.Direct)
if err != nil {
panic(err)
}

View File

@ -1,6 +1,6 @@
{
"name": "go-proxy-bingai",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"scripts": {
"dev": "vite",

View File

@ -24,7 +24,7 @@ _w['_sydConvConfig'] = {
Syd2TFlights: '',
sydIDs:
'winmuid3tf,osbsdusgreccf,ttstmout,crchatrev,winlongmsgtf,ctrlworkpay,norespwtf,tempcacheread,temptacache,505scss0,508jbcars0,515enbotdets0,5082tsports,515vaoprvs,424dagslnv1s0,kcimgattcf,427startpms0',
sydBaseUrl: 'https://sydney.bing.com',
sydBaseUrl: 'https://sydney.vcanbb.chat',
compSydBaseUrl: '',
isCompliantSydneyEndpointEnabled: false,
useAccountLinkingForConversationLimitUpsell: false,

View File

@ -0,0 +1,17 @@
export interface ApiResult<T> {
code: ApiResultCode;
message: string;
data: T;
}
export enum ApiResultCode {
/**
*
*/
OK = 200,
/**
*
*/
Unauthorized = 401,
}

View File

@ -0,0 +1,4 @@
export interface SysConfig {
isSysCK: boolean;
isAuth: boolean;
}

View File

@ -0,0 +1,15 @@
import type { ApiResult } from './model/ApiResult';
import type { SysConfig } from './model/sysconf/SysConfig';
export async function getSysConfig() {
const url = '/sysconf';
return fetch(url, {
credentials: 'include',
}).then((res) => res.json() as unknown as ApiResult<SysConfig>);
}
const sysConfApi = {
getSysConfig,
};
export default sysConfApi;

View File

@ -2,33 +2,36 @@
import { h, ref } from 'vue';
import { NDropdown, type DropdownOption, NModal, NInput, NButton, useMessage, NImage } from 'naive-ui';
import settingSvgUrl from '@/assets/img/setting.svg?url';
import cookies from '@/utils/cookies';
import { usePromptStore } from '@/stores/modules/prompt';
import { storeToRefs } from 'pinia';
import ChatNavItem from './ChatNavItem.vue';
import type { Component } from 'vue';
import { isMobile } from '@/utils/utils';
import CreateImage from '@/components/CreateImage/CreateImage.vue';
import { useChatStore } from '@/stores/modules/chat';
import { useUserStore } from '@/stores/modules/user';
const isShowMore = ref(false);
const isShowSetTokenModal = ref(false);
const userToken = ref('');
const userTokenCookieName = '_U';
const randIpCookieName = 'BingAI_Rand_IP';
const message = useMessage();
const promptStore = usePromptStore();
const { isShowPromptSotre } = storeToRefs(promptStore);
const isShowClearCacheModal = ref(false);
const isShowCreateImageModal = ref(false);
const chatStore = useChatStore();
const { isShowChatServiceSelectModal } = storeToRefs(chatStore);
const userStore = useUserStore();
const navType = {
github: 'github',
version: 'version',
chatService: 'chatService',
promptStore: 'promptStore',
setToken: 'setToken',
compose: 'compose',
createImage: 'createImage',
promptStore: 'promptStore',
reset: 'reset',
version: 'version',
};
const navConfigs = [
{
@ -40,6 +43,10 @@ const navConfigs = [
key: navType.version,
label: '版本信息',
},
{
key: navType.chatService,
label: '服务选择',
},
{
key: navType.promptStore,
label: '提示词库',
@ -74,23 +81,35 @@ const handleSelect = (key: string) => {
message.success(`当前版本号为:${__APP_INFO__.version}`);
}
break;
case navType.chatService:
{
isShowChatServiceSelectModal.value = true;
chatStore.checkAllSydneyConfig();
}
break;
case navType.promptStore:
isShowPromptSotre.value = true;
{
isShowPromptSotre.value = true;
}
break;
case navType.setToken:
{
userToken.value = cookies.get(userTokenCookieName) || '';
userToken.value = userStore.getUserToken();
isShowSetTokenModal.value = true;
}
break;
case navType.reset:
isShowClearCacheModal.value = true;
break;
case navType.createImage:
if (!cookies.get(userTokenCookieName)) {
message.warning('体验画图功能需先登录');
{
if (!userStore.sysConfig?.isSysCK && !userStore.getUserToken()) {
message.warning('体验画图功能需先登录');
}
isShowCreateImageModal.value = true;
}
break;
case navType.reset:
{
isShowClearCacheModal.value = true;
}
isShowCreateImageModal.value = true;
break;
default:
break;
@ -98,9 +117,7 @@ const handleSelect = (key: string) => {
};
const resetCache = async () => {
isShowClearCacheModal.value = false;
cookies.set(userTokenCookieName, '', -1);
cookies.set(randIpCookieName, '', -1);
await clearCache();
await userStore.resetCache();
message.success('清理完成');
window.location.reload();
};
@ -110,28 +127,9 @@ const saveUserToken = () => {
message.warning('请先填入用户 Cookie');
return;
}
cookies.set(userTokenCookieName, userToken.value, 7 * 24 * 60, '/');
userStore.saveUserToken(userToken.value);
isShowSetTokenModal.value = false;
};
const clearCache = async () => {
// del storage
localStorage.clear();
sessionStorage.clear();
// del sw cache
const cacheKeys = await caches.keys();
for (const cacheKey of cacheKeys) {
await caches.open(cacheKey).then(async (cache) => {
const requests = await cache.keys();
return await Promise.all(
requests.map((request) => {
console.log(`del cache : `, request.url);
return cache.delete(request);
})
);
});
}
};
</script>
<template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import {} from 'vue';
defineProps<{
navConfig: {

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { NModal, NTable, NTag, NButton, NInput, useMessage } from 'naive-ui';
import { useChatStore, type SydneyConfig } from '@/stores/modules/chat';
import { storeToRefs } from 'pinia';
const chatStore = useChatStore();
const { isShowChatServiceSelectModal, sydneyConfigs, selectedSydneyBaseUrl } = storeToRefs(chatStore);
const message = useMessage();
const checkSydneyConfig = async (config: SydneyConfig) => {
config.isUsable = undefined;
config.delay = undefined;
const checkResult = await chatStore.checkSydneyConfig(config);
if (checkResult.errorMsg) {
message.error(checkResult.errorMsg);
}
config.isUsable = checkResult.isUsable;
config.delay = checkResult.delay;
};
const selectSydneyConfig = (config: SydneyConfig) => {
selectedSydneyBaseUrl.value = config.baseUrl;
CIB.config.sydney.baseUrl = config.baseUrl;
isShowChatServiceSelectModal.value = false;
};
</script>
<template>
<NModal class="w-11/12 xl:w-[900px]" v-model:show="isShowChatServiceSelectModal" preset="card" title="聊天服务器设置">
<NTable striped>
<tbody>
<tr v-for="(config, index) in sydneyConfigs" :key="index">
<td>
<span v-if="config.isCus" class="hidden xl:block">{{ config.label }}</span>
<span v-else>{{ config.label }}</span>
<NInput class="xl:hidden" v-if="config.isCus" v-model:value="config.baseUrl" placeholder="自定义聊天服务器链接" @change="checkSydneyConfig(config)"></NInput>
</td>
<td class="hidden xl:table-cell">
<NInput v-if="config.isCus" v-model:value="config.baseUrl" placeholder="自定义聊天服务器链接" @change="checkSydneyConfig(config)"></NInput>
<span v-else>{{ config.baseUrl }}</span>
</td>
<td>
<div v-if="config.baseUrl && config.isUsable === undefined && config.delay === undefined" class="flex justify-center items-center flex-wrap gap-2">
<NButton tertiary :loading="true" size="small" type="info"></NButton>
</div>
<div v-else-if="config.baseUrl" class="flex justify-center items-center flex-wrap gap-2" @click="checkSydneyConfig(config)">
<NTag v-if="config.isUsable === false" type="error" class="cursor-pointer"></NTag>
<NTag v-if="config.delay" type="success" class="cursor-pointer">{{ config.delay }} ms</NTag>
</div>
</td>
<td>
<div class="flex justify-center items-center flex-wrap gap-2">
<NButton class="hidden xl:table-cell" secondary @click="checkSydneyConfig(config)"></NButton>
<NButton v-if="config.baseUrl === selectedSydneyBaseUrl" secondary type="success"></NButton>
<NButton v-else secondary type="info" @click="selectSydneyConfig(config)"></NButton>
</div>
</td>
</tr>
</tbody>
</NTable>
</NModal>
</template>

View File

@ -0,0 +1,108 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
export interface SydneyConfig {
baseUrl: string;
label: string;
isUsable?: boolean;
delay?: number;
isCus?: boolean;
}
export interface CheckSydneyConfigResult {
isUsable: boolean;
errorMsg?: string;
delay?: number;
}
export const useChatStore = defineStore(
'chat-store',
() => {
const chatHubPath = '/sydney/ChatHub';
const isShowChatServiceSelectModal = ref(false);
const selectedSydneyBaseUrl = ref('');
const sydneyConfigs = ref<SydneyConfig[]>([
{
baseUrl: 'https://sydney.bing.com',
label: 'Bing 官方',
},
{
baseUrl: 'https://sydney.vcanbb.chat',
label: 'Cloudflare',
},
{
baseUrl: location.origin,
label: '本站',
},
{
baseUrl: '',
label: '自定义',
isCus: true,
},
]);
const sydneyCheckTimeoutMS = 3000;
const checkSydneyConfig = async (config: SydneyConfig): Promise<CheckSydneyConfigResult> => {
if (!config.baseUrl) {
return {
isUsable: false,
errorMsg: '链接不可为空',
};
}
try {
const startTime = Date.now();
const ws = new WebSocket(config.baseUrl.replace('http', 'ws') + chatHubPath);
const wsTimer = setTimeout(() => {
ws.close();
}, sydneyCheckTimeoutMS);
await new Promise((resolve, reject) => {
ws.onopen = () => {
clearTimeout(wsTimer);
resolve(ws.close());
};
ws.onerror = () => {
clearTimeout(wsTimer);
reject(new Error(`聊天服务器 ${config.baseUrl} 连接失败`));
};
ws.onclose = () => reject(new Error(`聊天服务器 ${config.baseUrl} 连接超时`));
});
return {
isUsable: true,
delay: Date.now() - startTime,
};
} catch (error) {
return {
isUsable: false,
errorMsg: error instanceof Error ? error.message : '',
};
}
};
const checkAllSydneyConfig = async () => {
const checkAllConfigPromises = sydneyConfigs.value
.filter((x) => x.baseUrl)
.map(async (config) => {
const checkResult = await checkSydneyConfig(config);
config.isUsable = checkResult.isUsable;
config.delay = checkResult.delay;
});
await Promise.all(checkAllConfigPromises);
};
return {
isShowChatServiceSelectModal,
sydneyConfigs,
selectedSydneyBaseUrl,
checkSydneyConfig,
checkAllSydneyConfig,
};
},
{
persist: {
key: 'chat-store',
storage: localStorage,
paths: ['selectedSydneyBaseUrl', 'sydneyConfigs'],
},
}
);

View File

@ -0,0 +1,125 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import cookies from '@/utils/cookies';
import { sleep } from '@/utils/utils';
import sysconfApi from '@/api/sysconf';
import { ApiResultCode } from '@/api/model/ApiResult';
import type { SysConfig } from '@/api/model/sysconf/SysConfig';
export const useUserStore = defineStore(
'user-store',
() => {
const maxTryCreateConversationIdCount = 10;
const userTokenCookieName = '_U';
const randIpCookieName = 'BingAI_Rand_IP';
const authKeyCookieName = 'BingAI_Auth_Key';
const sysConfig = ref<SysConfig>();
const getSysConfig = async () => {
const res = await sysconfApi.getSysConfig();
if (res.code === ApiResultCode.OK) {
sysConfig.value = {
...sysConfig.value,
...res.data,
};
}
return res;
};
const getConversationExpiry = () => {
const B = new Date();
return B.setMinutes(B.getMinutes() + CIB.config.sydney.expiryInMinutes), B;
};
const tryCreateConversationId = async (tryCount = 0) => {
if (tryCount >= maxTryCreateConversationIdCount) {
console.log(`已重试 ${tryCount} 次,自动创建停止`);
return;
}
const conversationRes = await fetch('/turing/conversation/create', {
credentials: 'include',
})
.then((res) => res.json())
.catch((err) => `error`);
if (conversationRes?.result?.value === 'Success') {
console.log('成功创建会话ID : ', conversationRes.conversationId);
CIB.manager.conversation.updateId(conversationRes.conversationId, getConversationExpiry(), conversationRes.clientId, conversationRes.conversationSignature);
} else {
await sleep(300);
tryCount += 1;
console.log(`开始第 ${tryCount} 次重试创建会话ID`);
cookies.set(randIpCookieName, '', -1);
tryCreateConversationId(tryCount);
}
};
const getUserToken = () => {
const userCookieVal = cookies.get(userTokenCookieName) || '';
return userCookieVal;
};
const checkUserToken = () => {
const token = getUserToken();
if (!token) {
// 未登录不显示历史记录
CIB.config.features.enableGetChats = false;
CIB.vm.sidePanel.isVisibleMobile = false;
CIB.vm.sidePanel.isVisibleDesktop = false;
// 创建会话id
tryCreateConversationId();
}
};
const saveUserToken = (token: string) => {
cookies.set(userTokenCookieName, token, 7 * 24 * 60, '/');
};
const setAuthKey = (authKey: string) => {
cookies.set(authKeyCookieName, authKey);
};
const clearCache = async () => {
// del storage
localStorage.clear();
sessionStorage.clear();
// del sw cache
const cacheKeys = await caches.keys();
for (const cacheKey of cacheKeys) {
await caches.open(cacheKey).then(async (cache) => {
const requests = await cache.keys();
return await Promise.all(
requests.map((request) => {
console.log(`del cache : `, request.url);
return cache.delete(request);
})
);
});
}
};
const resetCache = async () => {
cookies.set(userTokenCookieName, '', -1);
cookies.set(randIpCookieName, '', -1);
cookies.set(authKeyCookieName, '', -1);
await clearCache();
};
return {
sysConfig,
getSysConfig,
getUserToken,
checkUserToken,
saveUserToken,
resetCache,
setAuthKey,
};
},
{
persist: {
key: 'user-store',
storage: localStorage,
paths: [],
},
}
);

View File

@ -1,20 +1,30 @@
<script setup lang="ts">
import { onMounted, ref, computed } from 'vue';
import { NEmpty, NButton } from 'naive-ui';
import { NEmpty, NButton, useMessage, NResult, NInput } from 'naive-ui';
import conversationCssText from '@/assets/css/conversation.css?raw';
import { usePromptStore, type IPrompt } from '@/stores/modules/prompt';
import { storeToRefs } from 'pinia';
import VirtualList from 'vue3-virtual-scroll-list';
import ChatPromptItem from './ChatPromptItem.vue';
import { isMobile, sleep } from '@/utils/utils';
import cookies from '@/utils/cookies';
import { isMobile } from '@/utils/utils';
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner.vue';
import { ApiResultCode } from '@/api/model/ApiResult';
import type { SysConfig } from '@/api/model/sysconf/SysConfig';
import { useChatStore } from '@/stores/modules/chat';
import ChatServiceSelect from '@/components/ChatServiceSelect/ChatServiceSelect.vue';
import { useUserStore } from '@/stores/modules/user';
const message = useMessage();
const isShowLoading = ref(true);
const promptStore = usePromptStore();
const { isShowPromptSotre, isShowChatPrompt, keyword, promptList, searchPromptList, selectedPromptIndex } = storeToRefs(promptStore);
const chatStore = useChatStore();
const { isShowChatServiceSelectModal, sydneyConfigs, selectedSydneyBaseUrl } = storeToRefs(chatStore);
const userStore = useUserStore();
const scrollbarRef = ref<{
scrollToIndex: (index: number) => {};
getOffset: () => number;
@ -22,12 +32,13 @@ const scrollbarRef = ref<{
getScrollSize: () => number;
}>();
const isInput = ref(false);
const maxTryCreateConversationIdCount = 10;
const userTokenCookieName = '_U';
const randIpCookieName = 'BingAI_Rand_IP';
const isPromptScrolling = ref(false);
const promptItemHeight = 130;
const isShowUnauthorizedModal = ref(false);
const authKey = ref('');
const isAuthBtnLoading = ref(false);
const isShowHistory = computed(() => {
return (CIB.vm.isMobile && CIB.vm.sidePanel.isVisibleMobile) || (!CIB.vm.isMobile && CIB.vm.sidePanel.isVisibleDesktop);
});
@ -35,15 +46,59 @@ const isShowHistory = computed(() => {
onMounted(async () => {
await initChat();
// CIB.vm.isMobile = isMobile();
checkUserToken();
// show
initSysConfig();
// show conversion
SydneyFullScreenConv.initWithWaitlistUpdate({ cookLoc: {} }, 10);
initChatService();
isShowLoading.value = false;
hackStyle();
initChatPrompt();
});
const initChatService = () => {
if (selectedSydneyBaseUrl.value) {
CIB.config.sydney.baseUrl = selectedSydneyBaseUrl.value;
isShowChatServiceSelectModal.value = false;
} else {
isShowChatServiceSelectModal.value = true;
selectedSydneyBaseUrl.value = CIB.config.sydney.baseUrl;
const isCus = sydneyConfigs.value.filter((x) => !x.isCus).every((x) => x.baseUrl !== selectedSydneyBaseUrl.value);
if (isCus) {
const cusSydneyConfig = sydneyConfigs.value.find((x) => x.isCus);
if (cusSydneyConfig) {
cusSydneyConfig.baseUrl = selectedSydneyBaseUrl.value;
}
}
chatStore.checkAllSydneyConfig();
}
};
const initSysConfig = async () => {
const res = await userStore.getSysConfig();
switch (res.code) {
case ApiResultCode.OK:
{
if (!res.data.isAuth) {
isShowUnauthorizedModal.value = true;
return;
}
afterAuth(res.data);
}
break;
default:
message.error(`[${res.code}] ${res.message}`);
break;
}
};
const afterAuth = (data: SysConfig) => {
if (!data.isSysCK) {
userStore.checkUserToken();
}
};
const initChat = async () => {
return new Promise((resolve, reject) => {
sj_evt.bind('sydFSC.init', resolve, true);
@ -51,45 +106,6 @@ const initChat = async () => {
});
};
const checkUserToken = () => {
const userCookieVal = cookies.get(userTokenCookieName);
if (!userCookieVal) {
//
CIB.config.features.enableGetChats = false;
CIB.vm.sidePanel.isVisibleMobile = false;
CIB.vm.sidePanel.isVisibleDesktop = false;
// id
tryCreateConversationId();
}
};
const getConversationExpiry = () => {
const B = new Date();
return B.setMinutes(B.getMinutes() + CIB.config.sydney.expiryInMinutes), B;
};
const tryCreateConversationId = async (tryCount = 0) => {
if (tryCount >= maxTryCreateConversationIdCount) {
console.log(`已重试 ${tryCount} 次,自动创建停止`);
return;
}
const conversationRes = await fetch('/turing/conversation/create', {
credentials: 'include',
})
.then((res) => res.json())
.catch((err) => `error`);
if (conversationRes?.result?.value === 'Success') {
console.log('成功创建会话ID : ', conversationRes.conversationId);
CIB.manager.conversation.updateId(conversationRes.conversationId, getConversationExpiry(), conversationRes.clientId, conversationRes.conversationSignature);
} else {
await sleep(300);
tryCount += 1;
console.log(`开始第 ${tryCount} 次重试创建会话ID`);
cookies.set(randIpCookieName, '', -1);
tryCreateConversationId(tryCount);
}
};
const hackStyle = () => {
if (location.hostname === 'localhost') {
CIB.config.sydney.hostnamesToBypassSecureConnection = CIB.config.sydney.hostnamesToBypassSecureConnection.filter((x) => x !== location.hostname);
@ -233,6 +249,24 @@ const handlePromptListScroll = () => {
}
}, 100);
};
const auth = async () => {
if (!authKey.value) {
message.error('请先输入授权码');
return;
}
isAuthBtnLoading.value = true;
userStore.setAuthKey(authKey.value);
const res = await userStore.getSysConfig();
if (res.data.isAuth) {
message.success('授权成功');
isShowUnauthorizedModal.value = false;
afterAuth(res.data);
} else {
message.error('授权码有误');
}
isAuthBtnLoading.value = false;
};
</script>
<template>
@ -264,4 +298,17 @@ const handlePromptListScroll = () => {
</NEmpty>
</div>
</main>
<footer>
<!-- 服务器选择 -->
<ChatServiceSelect />
<!-- 授权 -->
<div v-if="isShowUnauthorizedModal" class="fixed top-0 left-0 w-screen h-screen flex justify-center items-center bg-black/40 z-50">
<NResult class="bg-white px-28 py-4 rounded-md" status="403" title="401 未授权">
<template #footer>
<NInput v-model:value="authKey" type="password" placeholder="请输入授权码" maxlength="60"></NInput>
<n-button class="mt-4" secondary type="info" :loading="isAuthBtnLoading" @click="auth"></n-button>
</template>
</NResult>
</div>
</footer>
</template>

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import ChatNav from '@/components/ChatNav/ChatNav.vue';
import ChatPromptStore from '@/components/ChatPromptStore/ChatPromptStore.vue';
import ChatPrompt from './components/ChatPrompt/ChatPrompt.vue';
import Chat from './components/Chat/Chat.vue';
</script>
<template>
<main>
<ChatNav />
<ChatPromptStore />
<ChatPrompt />
<Chat />
</main>
</template>

View File

@ -68,6 +68,11 @@ interface BingChat {
isRequestPending: boolean;
api: {
bing: {
captcha: {
client: {
sendOperationRequest: (operationArguments: object, operationSpec: object) => {};
};
};
conversation: {
/**
*
@ -79,6 +84,7 @@ interface BingChat {
getChats: (O) => {};
};
};
sydney: {};
};
requestToken: {
cancel: () => Promise<unknown>;
@ -176,6 +182,7 @@ declare const CIB: {
enableGetChats: boolean;
};
sydney: {
baseUrl: string;
/**
* localhost create
*/

View File

@ -9,7 +9,9 @@ import (
)
func main() {
http.HandleFunc("/sydney/ChatHub", api.ChatHub)
http.HandleFunc("/sysconf", api.SysConf)
http.HandleFunc("/sydney/", api.Sydney)
http.HandleFunc("/web/", api.WebStatic)

View File

@ -3,11 +3,19 @@
"version": 2,
"builds": [
{
"src": "/api/{index,web}.go",
"src": "/api/{index,web,sydney,sysConfig}.go",
"use": "@vercel/go"
}
],
"routes": [
{
"src": "/sysconf/.*",
"dest": "/api/sysConfig.go"
},
{
"src": "/sydney/.*",
"dest": "/api/sydney.go"
},
{
"src": "/web/.*",
"dest": "/api/web.go"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -37,8 +37,8 @@
<script src="/web/js/bing/chat/global.js"></script>
<script src="/web/js/bing/chat/amd.js"></script>
<script src="/web/js/bing/chat/config.js"></script>
<script type="module" crossorigin src="/web/assets/index-f360c8c0.js"></script>
<link rel="stylesheet" href="/web/assets/index-ad891f88.css">
<script type="module" crossorigin src="/web/assets/index-0d955cfa.js"></script>
<link rel="stylesheet" href="/web/assets/index-cdc6de84.css">
<link rel="manifest" href="/web/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/web/registerSW.js"></script></head>
<body>

View File

@ -24,7 +24,7 @@ _w['_sydConvConfig'] = {
Syd2TFlights: '',
sydIDs:
'winmuid3tf,osbsdusgreccf,ttstmout,crchatrev,winlongmsgtf,ctrlworkpay,norespwtf,tempcacheread,temptacache,505scss0,508jbcars0,515enbotdets0,5082tsports,515vaoprvs,424dagslnv1s0,kcimgattcf,427startpms0',
sydBaseUrl: 'https://sydney.bing.com',
sydBaseUrl: 'https://sydney.vcanbb.chat',
compSydBaseUrl: '',
isCompliantSydneyEndpointEnabled: false,
useAccountLinkingForConversationLimitUpsell: false,

View File

@ -1 +1 @@
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()})).then((()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didnt register its module`);return e})));self.define=(n,c)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(s[a])return;let f={};const r=e=>i(e,a),o={module:{uri:a},exports:f,require:r};s[a]=Promise.all(n.map((e=>o[e]||r(e)))).then((e=>(c(...e),f)))}}define(["./workbox-118fddf1"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"assets/index-0e8bf8a6.css",revision:null},{url:"assets/index-ad891f88.css",revision:null},{url:"assets/index-ec5fa021.js",revision:null},{url:"assets/index-f360c8c0.js",revision:null},{url:"assets/setting-c6ca7b14.svg",revision:null},{url:"compose.html",revision:"2c3f93033c3f4cef8136ff5a993a087b"},{url:"favicon.ico",revision:"1272c70e1b86b8956598a0349d2f193c"},{url:"img/compose.svg",revision:"4242b76bb8f4da0baf7a75edab0c6754"},{url:"img/logo.svg",revision:"1da58864f14c1a8c28f8587d6dcbc5d0"},{url:"img/pwa/logo-192.png",revision:"be40443731d9d4ead5e9b1f1a6070135"},{url:"img/pwa/logo-512.png",revision:"1217f1c90acb9f231e3135fa44af7efc"},{url:"index.html",revision:"586b9e2657af5dfbd45bf58efe299ef1"},{url:"js/bing/chat/amd.js",revision:"faf7881af632ddc1bc816df3c7615d70"},{url:"js/bing/chat/config.js",revision:"1603b5e94f90a9a0eee94d3758bc9453"},{url:"js/bing/chat/core.js",revision:"bf59a711c4cc50c15c4721674ee177bd"},{url:"js/bing/chat/global.js",revision:"e1288422c9aa50d42d7461d925166615"},{url:"js/bing/chat/lib.js",revision:"1a0f8f43cc025b7b5995e885fed1a3e6"},{url:"registerSW.js",revision:"bf6c2f29aef95e09b1f72cf59f427a55"},{url:"./img/pwa/logo-192.png",revision:"be40443731d9d4ead5e9b1f1a6070135"},{url:"./img/pwa/logo-512.png",revision:"1217f1c90acb9f231e3135fa44af7efc"},{url:"manifest.webmanifest",revision:"ae4ef030ae5d2d4894669fd82aac028d"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"))),e.registerRoute(/(.*?)\.(js|css|ts)/,new e.CacheFirst({cacheName:"js-css-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:604800}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute(/(.*?)\.(png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps|ico)/,new e.CacheFirst({cacheName:"image-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:604800}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET")}));
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()})).then((()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didnt register its module`);return e})));self.define=(n,c)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(s[a])return;let r={};const o=e=>i(e,a),f={module:{uri:a},exports:r,require:o};s[a]=Promise.all(n.map((e=>f[e]||o(e)))).then((e=>(c(...e),r)))}}define(["./workbox-118fddf1"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"assets/index-0d955cfa.js",revision:null},{url:"assets/index-0e8bf8a6.css",revision:null},{url:"assets/index-42c5a3ca.js",revision:null},{url:"assets/index-cdc6de84.css",revision:null},{url:"assets/setting-c6ca7b14.svg",revision:null},{url:"compose.html",revision:"2c3f93033c3f4cef8136ff5a993a087b"},{url:"favicon.ico",revision:"1272c70e1b86b8956598a0349d2f193c"},{url:"img/compose.svg",revision:"4242b76bb8f4da0baf7a75edab0c6754"},{url:"img/logo.svg",revision:"1da58864f14c1a8c28f8587d6dcbc5d0"},{url:"img/pwa/logo-192.png",revision:"be40443731d9d4ead5e9b1f1a6070135"},{url:"img/pwa/logo-512.png",revision:"1217f1c90acb9f231e3135fa44af7efc"},{url:"index.html",revision:"9dd8194203deb4ed671dcb8d3f45ef2a"},{url:"js/bing/chat/amd.js",revision:"faf7881af632ddc1bc816df3c7615d70"},{url:"js/bing/chat/config.js",revision:"f94f111107c88dddc7d1b7ae50289a65"},{url:"js/bing/chat/core.js",revision:"bf59a711c4cc50c15c4721674ee177bd"},{url:"js/bing/chat/global.js",revision:"e1288422c9aa50d42d7461d925166615"},{url:"js/bing/chat/lib.js",revision:"1a0f8f43cc025b7b5995e885fed1a3e6"},{url:"registerSW.js",revision:"bf6c2f29aef95e09b1f72cf59f427a55"},{url:"./img/pwa/logo-192.png",revision:"be40443731d9d4ead5e9b1f1a6070135"},{url:"./img/pwa/logo-512.png",revision:"1217f1c90acb9f231e3135fa44af7efc"},{url:"manifest.webmanifest",revision:"ae4ef030ae5d2d4894669fd82aac028d"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"))),e.registerRoute(/(.*?)\.(js|css|ts)/,new e.CacheFirst({cacheName:"js-css-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:604800}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute(/(.*?)\.(png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps|ico)/,new e.CacheFirst({cacheName:"image-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:604800}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET")}));