mirror of
https://github.com/adams549659584/go-proxy-bingai.git
synced 2025-06-03 15:02:30 +08:00
feat: ⚡ 可选聊天服务器、cloudflare 聊天服务器部署
This commit is contained in:
parent
842e02b9d7
commit
bfd58c5a52
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@ -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",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
29
README.md
29
README.md
@ -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
|
||||
|
||||
一键部署,点这里 => [](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)
|
||||
|
||||

|
||||
@ -190,11 +201,23 @@ RAILWAY_DOCKERFILE_PATH=docker/Dockerfile
|
||||
|
||||

|
||||
|
||||
## 部署聊天服务器
|
||||
|
||||
> 核心代码 [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] 简单访问权限控制
|
||||
|
@ -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)
|
||||
}
|
48
api/helper/helper.go
Normal file
48
api/helper/helper.go
Normal 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
|
||||
}
|
@ -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
Normal file
15
api/sydney.go
Normal 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
Normal file
24
api/sysConfig.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
124
cloudflare/worker.js
Normal file
124
cloudflare/worker.js
Normal 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;
|
||||
},
|
||||
};
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "go-proxy-bingai",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -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,
|
||||
|
17
frontend/src/api/model/ApiResult.ts
Normal file
17
frontend/src/api/model/ApiResult.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export interface ApiResult<T> {
|
||||
code: ApiResultCode;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export enum ApiResultCode {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
OK = 200,
|
||||
|
||||
/**
|
||||
* 未授权
|
||||
*/
|
||||
Unauthorized = 401,
|
||||
}
|
4
frontend/src/api/model/sysconf/SysConfig.ts
Normal file
4
frontend/src/api/model/sysconf/SysConfig.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface SysConfig {
|
||||
isSysCK: boolean;
|
||||
isAuth: boolean;
|
||||
}
|
15
frontend/src/api/sysconf.ts
Normal file
15
frontend/src/api/sysconf.ts
Normal 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;
|
@ -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>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import {} from 'vue';
|
||||
|
||||
defineProps<{
|
||||
navConfig: {
|
||||
|
@ -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>
|
108
frontend/src/stores/modules/chat/index.ts
Normal file
108
frontend/src/stores/modules/chat/index.ts
Normal 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'],
|
||||
},
|
||||
}
|
||||
);
|
125
frontend/src/stores/modules/user/index.ts
Normal file
125
frontend/src/stores/modules/user/index.ts
Normal 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: [],
|
||||
},
|
||||
}
|
||||
);
|
@ -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>
|
@ -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>
|
||||
|
7
frontend/types/global.d.ts
vendored
7
frontend/types/global.d.ts
vendored
@ -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
|
||||
*/
|
||||
|
4
main.go
4
main.go
@ -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)
|
||||
|
||||
|
10
vercel.json
10
vercel.json
@ -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
1266
web/assets/index-42c5a3ca.js
Normal file
1266
web/assets/index-42c5a3ca.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
web/assets/index-cdc6de84.css
Normal file
1
web/assets/index-cdc6de84.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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} didn’t 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} didn’t 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")}));
|
||||
|
Loading…
x
Reference in New Issue
Block a user