mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	添加会话授权支持
This commit is contained in:
		@@ -8,6 +8,7 @@
 | 
			
		||||
* [ ] 使用 MySQL 保存用户的聊天的历史记录
 | 
			
		||||
* [ ] 用户聊天鉴权,设置口令模式
 | 
			
		||||
* [ ] 每次连接自动加载历史记录
 | 
			
		||||
* [ ] OpenAI API 负载均衡,限制每个 API Key 每分钟之内调用次数不超过 15次,防止被封
 | 
			
		||||
* [ ] 角色设定,预设一些角色,比如程序员,产品经理,医生,作家,老师...
 | 
			
		||||
* [ ] markdown 语法解析
 | 
			
		||||
* [ ] 用户配置界面
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,12 @@ import (
 | 
			
		||||
 | 
			
		||||
// ConfigSetHandle set configs
 | 
			
		||||
func (s *Server) ConfigSetHandle(c *gin.Context) {
 | 
			
		||||
	token := c.Query("token")
 | 
			
		||||
	if token != "RockYang" {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: types.ErrorMsg})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data map[string]string
 | 
			
		||||
	err := json.NewDecoder(c.Request.Body).Decode(&data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -71,6 +77,23 @@ func (s *Server) ConfigSetHandle(c *gin.Context) {
 | 
			
		||||
		s.Config.Chat.EnableContext = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// enable auth
 | 
			
		||||
	if enableAuth, ok := data["enable_auth"]; ok {
 | 
			
		||||
		v, err := strconv.ParseBool(enableAuth)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(http.StatusOK, types.BizVo{
 | 
			
		||||
				Code:    types.InvalidParams,
 | 
			
		||||
				Message: "enable_auth must be a bool parameter",
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		s.Config.EnableAuth = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if token, ok := data["token"]; ok {
 | 
			
		||||
		s.Config.Tokens = append(s.Config.Tokens, token)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 保存配置文件
 | 
			
		||||
	logger.Infof("Config: %+v", s.Config)
 | 
			
		||||
	err = types.SaveConfig(s.Config, s.ConfigPath)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/gin-contrib/sessions"
 | 
			
		||||
	"github.com/gin-contrib/sessions/cookie"
 | 
			
		||||
@@ -12,6 +13,7 @@ import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	logger2 "openai/logger"
 | 
			
		||||
	"openai/types"
 | 
			
		||||
	"openai/utils"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -35,6 +37,8 @@ type Server struct {
 | 
			
		||||
	ConfigPath string
 | 
			
		||||
	Client     *http.Client
 | 
			
		||||
	History    map[string][]types.Message
 | 
			
		||||
 | 
			
		||||
	WsSession map[string]string // 关闭 Websocket 会话
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServer(configPath string) (*Server, error) {
 | 
			
		||||
@@ -55,7 +59,9 @@ func NewServer(configPath string) (*Server, error) {
 | 
			
		||||
		Config:     config,
 | 
			
		||||
		Client:     client,
 | 
			
		||||
		ConfigPath: configPath,
 | 
			
		||||
		History:    make(map[string][]types.Message, 16)}, nil
 | 
			
		||||
		History:    make(map[string][]types.Message, 16),
 | 
			
		||||
		WsSession:  make(map[string]string),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) Run(webRoot embed.FS, path string) {
 | 
			
		||||
@@ -63,9 +69,11 @@ func (s *Server) Run(webRoot embed.FS, path string) {
 | 
			
		||||
	engine := gin.Default()
 | 
			
		||||
	engine.Use(sessionMiddleware(s.Config))
 | 
			
		||||
	engine.Use(corsMiddleware())
 | 
			
		||||
	engine.Use(AuthorizeMiddleware())
 | 
			
		||||
	engine.Use(AuthorizeMiddleware(s))
 | 
			
		||||
 | 
			
		||||
	engine.GET("/hello", Hello)
 | 
			
		||||
	engine.POST("/api/session/get", s.GetSessionHandle)
 | 
			
		||||
	engine.POST("/api/login", s.LoginHandle)
 | 
			
		||||
	engine.Any("/api/chat", s.ChatHandle)
 | 
			
		||||
	engine.POST("/api/config/set", s.ConfigSetHandle)
 | 
			
		||||
 | 
			
		||||
@@ -109,7 +117,7 @@ func corsMiddleware() gin.HandlerFunc {
 | 
			
		||||
			c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
 | 
			
		||||
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
 | 
			
		||||
			//允许跨域设置可以返回其他子段,可以自定义字段
 | 
			
		||||
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, Session-Name, Session")
 | 
			
		||||
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, ChatGPT-Token, Session")
 | 
			
		||||
			// 允许浏览器(客户端)可以解析的头部 (重要)
 | 
			
		||||
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
 | 
			
		||||
			//设置缓存时间
 | 
			
		||||
@@ -133,27 +141,63 @@ func corsMiddleware() gin.HandlerFunc {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AuthorizeMiddleware 用户授权验证
 | 
			
		||||
func AuthorizeMiddleware() gin.HandlerFunc {
 | 
			
		||||
func AuthorizeMiddleware(s *Server) gin.HandlerFunc {
 | 
			
		||||
	return func(c *gin.Context) {
 | 
			
		||||
		c.Next()
 | 
			
		||||
		//if c.Request.URL.Path == "/login" {
 | 
			
		||||
		//	c.Next()
 | 
			
		||||
		//	return
 | 
			
		||||
		//}
 | 
			
		||||
		if !s.Config.EnableAuth || c.Request.URL.Path == "/api/login" || c.Request.URL.Path == "/api/config/set" {
 | 
			
		||||
			c.Next()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//sessionName := c.GetHeader("Session-Name")
 | 
			
		||||
		//session, err := c.Cookie(sessionName)
 | 
			
		||||
		//if err == nil {
 | 
			
		||||
		//	c.Request.Header.Set(utils.SessionKey, session)
 | 
			
		||||
		//	c.Next()
 | 
			
		||||
		//} else {
 | 
			
		||||
		//	logger.Fatal(err)
 | 
			
		||||
		//	c.Abort()
 | 
			
		||||
		//	c.JSON(http.StatusUnauthorized, "No session data found")
 | 
			
		||||
		//}
 | 
			
		||||
		tokenName := c.GetHeader("Sec-WebSocket-Protocol")
 | 
			
		||||
		logger.Info(s.WsSession)
 | 
			
		||||
		logger.Info(tokenName)
 | 
			
		||||
		if addr, ok := s.WsSession[tokenName]; ok && addr == c.ClientIP() {
 | 
			
		||||
			c.Next()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tokenName = c.GetHeader(types.TokenName)
 | 
			
		||||
		session := sessions.Default(c)
 | 
			
		||||
		user := session.Get(tokenName)
 | 
			
		||||
		if user != nil {
 | 
			
		||||
			c.Set(types.SessionKey, user)
 | 
			
		||||
			c.Next()
 | 
			
		||||
		} else {
 | 
			
		||||
			c.Abort()
 | 
			
		||||
			c.JSON(http.StatusOK, types.BizVo{
 | 
			
		||||
				Code:    types.NotAuthorized,
 | 
			
		||||
				Message: "Not Authorized",
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) GetSessionHandle(c *gin.Context) {
 | 
			
		||||
	session := sessions.Default(c)
 | 
			
		||||
	c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Data: session.Get(types.TokenName)})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) LoginHandle(c *gin.Context) {
 | 
			
		||||
	var data map[string]string
 | 
			
		||||
	err := json.NewDecoder(c.Request.Body).Decode(&data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: types.ErrorMsg})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	token := data["token"]
 | 
			
		||||
	if !utils.ContainsItem(s.Config.Tokens, token) {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Invalid token"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sessionId := utils.RandString(42)
 | 
			
		||||
	session := sessions.Default(c)
 | 
			
		||||
	session.Set(sessionId, token)
 | 
			
		||||
	// 记录客户端 IP 地址
 | 
			
		||||
	s.WsSession[sessionId] = c.ClientIP()
 | 
			
		||||
	c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Data: sessionId})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Hello(c *gin.Context) {
 | 
			
		||||
	c.JSON(http.StatusOK, gin.H{"code": 0, "message": fmt.Sprintf("HELLO, XWEBSSH !!!")})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,12 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Listen   string
 | 
			
		||||
	Session  Session
 | 
			
		||||
	ProxyURL string
 | 
			
		||||
	Chat     Chat
 | 
			
		||||
	Listen     string
 | 
			
		||||
	Session    Session
 | 
			
		||||
	ProxyURL   string
 | 
			
		||||
	Chat       Chat
 | 
			
		||||
	EnableAuth bool     // 是否开启鉴权
 | 
			
		||||
	Tokens     []string // 授权的白名单列表 TODO: 后期要存储到 LevelDB 或者 Mysql 数据库
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Chat configs struct
 | 
			
		||||
@@ -60,6 +62,7 @@ func NewDefaultConfig() *Config {
 | 
			
		||||
			Temperature:   1.0,
 | 
			
		||||
			EnableContext: true,
 | 
			
		||||
		},
 | 
			
		||||
		EnableAuth: true,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,5 +31,9 @@ const (
 | 
			
		||||
	InvalidParams = BizCode(101) // 非法参数
 | 
			
		||||
	NotAuthorized = BizCode(400) // 未授权
 | 
			
		||||
 | 
			
		||||
	OkMsg = "Success"
 | 
			
		||||
	OkMsg    = "Success"
 | 
			
		||||
	ErrorMsg = "系统开小差了"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const TokenName = "ChatGPT-Token"
 | 
			
		||||
const SessionKey = "WEB_SSH_SESSION"
 | 
			
		||||
 
 | 
			
		||||
@@ -30,3 +30,12 @@ func Long2IP(ipInt int64) string {
 | 
			
		||||
func IsBlank(value string) bool {
 | 
			
		||||
	return len(strings.TrimSpace(value)) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ContainsItem(slice []string, item string) bool {
 | 
			
		||||
	for _, e := range slice {
 | 
			
		||||
		if e == item {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,2 @@
 | 
			
		||||
VUE_APP_WS_HOST=ws://172.22.11.200:5678
 | 
			
		||||
VUE_APP_API_HOST=172.22.11.200:5678
 | 
			
		||||
VUE_APP_API_SECURE=false
 | 
			
		||||
@@ -1 +1,2 @@
 | 
			
		||||
VUE_APP_WS_HOST=ws://127.0.0.1:5678
 | 
			
		||||
VUE_APP_API_SECURE=false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										112
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										112
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -14,6 +14,7 @@
 | 
			
		||||
        "element-plus": "^2.1.11",
 | 
			
		||||
        "good-storage": "^1.1.1",
 | 
			
		||||
        "json-bigint": "^1.0.0",
 | 
			
		||||
        "qs": "^6.11.1",
 | 
			
		||||
        "vue": "^3.2.13",
 | 
			
		||||
        "vue-router": "^4.0.15"
 | 
			
		||||
      },
 | 
			
		||||
@@ -3624,6 +3625,18 @@
 | 
			
		||||
      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/body-parser/node_modules/qs": {
 | 
			
		||||
      "version": "6.9.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
      "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.6"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/bonjour-service": {
 | 
			
		||||
      "version": "1.0.12",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.0.12.tgz",
 | 
			
		||||
@@ -3712,7 +3725,6 @@
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "function-bind": "^1.1.1",
 | 
			
		||||
        "get-intrinsic": "^1.0.2"
 | 
			
		||||
@@ -5728,6 +5740,18 @@
 | 
			
		||||
      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/express/node_modules/qs": {
 | 
			
		||||
      "version": "6.9.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
      "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.6"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/express/node_modules/safe-buffer": {
 | 
			
		||||
      "version": "5.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
			
		||||
@@ -6013,8 +6037,7 @@
 | 
			
		||||
    "node_modules/function-bind": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/functional-red-black-tree": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
@@ -6044,7 +6067,6 @@
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "function-bind": "^1.1.1",
 | 
			
		||||
        "has": "^1.0.3",
 | 
			
		||||
@@ -6157,7 +6179,6 @@
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "function-bind": "^1.1.1"
 | 
			
		||||
      },
 | 
			
		||||
@@ -6187,7 +6208,6 @@
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
@@ -7682,6 +7702,14 @@
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/object-inspect": {
 | 
			
		||||
      "version": "1.12.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
 | 
			
		||||
      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/object-keys": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
 | 
			
		||||
@@ -8747,12 +8775,17 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/qs": {
 | 
			
		||||
      "version": "6.9.7",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
      "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "side-channel": "^1.0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.6"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/queue-microtask": {
 | 
			
		||||
@@ -9308,6 +9341,19 @@
 | 
			
		||||
      "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/side-channel": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.0",
 | 
			
		||||
        "get-intrinsic": "^1.0.2",
 | 
			
		||||
        "object-inspect": "^1.9.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/signal-exit": {
 | 
			
		||||
      "version": "3.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
 | 
			
		||||
@@ -13832,6 +13878,12 @@
 | 
			
		||||
          "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "qs": {
 | 
			
		||||
          "version": "6.9.7",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
          "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -13911,7 +13963,6 @@
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "function-bind": "^1.1.1",
 | 
			
		||||
        "get-intrinsic": "^1.0.2"
 | 
			
		||||
@@ -15514,6 +15565,12 @@
 | 
			
		||||
          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "qs": {
 | 
			
		||||
          "version": "6.9.7",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
          "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "safe-buffer": {
 | 
			
		||||
          "version": "5.2.1",
 | 
			
		||||
          "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
 | 
			
		||||
@@ -15745,8 +15802,7 @@
 | 
			
		||||
    "function-bind": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
 | 
			
		||||
    },
 | 
			
		||||
    "functional-red-black-tree": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
@@ -15770,7 +15826,6 @@
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "function-bind": "^1.1.1",
 | 
			
		||||
        "has": "^1.0.3",
 | 
			
		||||
@@ -15865,7 +15920,6 @@
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "function-bind": "^1.1.1"
 | 
			
		||||
      }
 | 
			
		||||
@@ -15888,8 +15942,7 @@
 | 
			
		||||
    "has-symbols": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
 | 
			
		||||
    },
 | 
			
		||||
    "hash-sum": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
@@ -17079,6 +17132,11 @@
 | 
			
		||||
      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "object-inspect": {
 | 
			
		||||
      "version": "1.12.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
 | 
			
		||||
      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
 | 
			
		||||
    },
 | 
			
		||||
    "object-keys": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
 | 
			
		||||
@@ -17844,10 +17902,12 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "qs": {
 | 
			
		||||
      "version": "6.9.7",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.9.7.tgz",
 | 
			
		||||
      "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "version": "6.11.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
 | 
			
		||||
      "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "side-channel": "^1.0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "queue-microtask": {
 | 
			
		||||
      "version": "1.2.3",
 | 
			
		||||
@@ -18317,6 +18377,16 @@
 | 
			
		||||
      "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "side-channel": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "call-bind": "^1.0.0",
 | 
			
		||||
        "get-intrinsic": "^1.0.2",
 | 
			
		||||
        "object-inspect": "^1.9.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "signal-exit": {
 | 
			
		||||
      "version": "3.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
    "element-plus": "^2.1.11",
 | 
			
		||||
    "good-storage": "^1.1.1",
 | 
			
		||||
    "json-bigint": "^1.0.0",
 | 
			
		||||
    "qs": "^6.11.1",
 | 
			
		||||
    "vue": "^3.2.13",
 | 
			
		||||
    "vue-router": "^4.0.15"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
/**
 | 
			
		||||
 * actions for chat page
 | 
			
		||||
 */
 | 
			
		||||
 */
 | 
			
		||||
@@ -24,10 +24,6 @@ export default defineComponent({
 | 
			
		||||
    icon: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'images/gpt-icon.png',
 | 
			
		||||
    },
 | 
			
		||||
    cursor: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,17 @@ import {createApp} from 'vue'
 | 
			
		||||
import ElementPlus from "element-plus"
 | 
			
		||||
import "element-plus/dist/index.css"
 | 
			
		||||
import App from './App.vue'
 | 
			
		||||
import Home from './views/Chat.vue'
 | 
			
		||||
import Chat from './views/Chat.vue'
 | 
			
		||||
import NotFound from './views/404.vue'
 | 
			
		||||
import './utils/prototype'
 | 
			
		||||
import "./assets/css/bootstrap.min.css"
 | 
			
		||||
import {Global} from "@/utils/storage";
 | 
			
		||||
 | 
			
		||||
Global['Chat'] = Chat
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
        name: 'home', path: '/', component: Home, meta: {
 | 
			
		||||
        name: 'home', path: '/', component: Chat, meta: {
 | 
			
		||||
            title: 'ChatGPT-Console'
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								web/src/utils/http.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								web/src/utils/http.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import {getSessionId} from "@/utils/storage";
 | 
			
		||||
 | 
			
		||||
axios.defaults.timeout = 5000
 | 
			
		||||
axios.defaults.baseURL = process.env.VUE_APP_API_SECURE === true ? 'https://' + process.env.VUE_APP_API_HOST : 'http://' + process.env.VUE_APP_API_HOST
 | 
			
		||||
axios.defaults.withCredentials = true
 | 
			
		||||
axios.defaults.headers.post['Content-Type'] = 'application/json'
 | 
			
		||||
 | 
			
		||||
// HTTP拦截器
 | 
			
		||||
axios.interceptors.request.use(
 | 
			
		||||
    config => {
 | 
			
		||||
        // set token
 | 
			
		||||
        config.headers['ChatGPT-Token'] = getSessionId();
 | 
			
		||||
        return config
 | 
			
		||||
    }, error => {
 | 
			
		||||
        return Promise.reject(error)
 | 
			
		||||
    })
 | 
			
		||||
axios.interceptors.response.use(
 | 
			
		||||
    response => {
 | 
			
		||||
        let data = response.data;
 | 
			
		||||
        if (data.code === 0) {
 | 
			
		||||
            return response
 | 
			
		||||
        } else {
 | 
			
		||||
            return Promise.reject(response.data)
 | 
			
		||||
        }
 | 
			
		||||
    }, error => {
 | 
			
		||||
        return Promise.reject(error)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// send a http get request
 | 
			
		||||
export function httpGet(url, params = {}) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        axios.get(url, {
 | 
			
		||||
            params: params
 | 
			
		||||
        }).then(response => {
 | 
			
		||||
            resolve(response.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// send a http post request
 | 
			
		||||
export function httpPost(url, data = {}, options = {}) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        axios.post(url, data, options).then(response => {
 | 
			
		||||
            resolve(response.data)
 | 
			
		||||
        }).catch(err => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								web/src/utils/storage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								web/src/utils/storage.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
/* eslint-disable no-constant-condition */
 | 
			
		||||
/**
 | 
			
		||||
 * storage handler
 | 
			
		||||
 */
 | 
			
		||||
import Storage from 'good-storage'
 | 
			
		||||
 | 
			
		||||
const SessionIdKey = 'ChatGPT_SESSION_ID';
 | 
			
		||||
export const Global = {}
 | 
			
		||||
 | 
			
		||||
export function getSessionId() {
 | 
			
		||||
    return Storage.get(SessionIdKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setSessionId(value) {
 | 
			
		||||
    Storage.set(SessionIdKey, value)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="body">
 | 
			
		||||
  <div class="body" v-loading="loading">
 | 
			
		||||
    <div id="container">
 | 
			
		||||
      <div class="tool-box">
 | 
			
		||||
        <el-image style="width: 24px; height: 24px" :src="logo"/>
 | 
			
		||||
        <el-button round>欢迎来到人工智能时代</el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-box" :style="{height: chatBoxHeight+'px'}">
 | 
			
		||||
        <div v-for="chat in chatData" :key="chat.id">
 | 
			
		||||
          <chat-prompt
 | 
			
		||||
@@ -9,7 +14,6 @@
 | 
			
		||||
              :content="chat.content"/>
 | 
			
		||||
          <chat-reply v-else-if="chat.type==='reply'"
 | 
			
		||||
                      :icon="chat.icon"
 | 
			
		||||
                      :cursor="chat.cursor"
 | 
			
		||||
                      :content="chat.content"/>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -25,14 +29,14 @@
 | 
			
		||||
              v-on:focus="focus"
 | 
			
		||||
              autofocus
 | 
			
		||||
              type="textarea"
 | 
			
		||||
              placeholder="Input any thing here..."
 | 
			
		||||
              placeholder="开始你的提问"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="btn-container">
 | 
			
		||||
          <el-row>
 | 
			
		||||
            <el-button type="success" class="send" :disabled="sending" v-on:click="sendMessage">发送</el-button>
 | 
			
		||||
            <el-button type="info" class="config" circle @click="showDialog = true">
 | 
			
		||||
            <el-button type="info" class="config" circle @click="showConnectDialog = true">
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Tools/>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
@@ -44,7 +48,28 @@
 | 
			
		||||
 | 
			
		||||
    </div><!-- end container -->
 | 
			
		||||
 | 
			
		||||
    <config-dialog v-model:show="showDialog"></config-dialog>
 | 
			
		||||
    <config-dialog v-model:show="showConnectDialog"></config-dialog>
 | 
			
		||||
 | 
			
		||||
    <div class="token-dialog">
 | 
			
		||||
      <el-dialog
 | 
			
		||||
          v-model="showLoginDialog"
 | 
			
		||||
          :show-close="false"
 | 
			
		||||
          :close-on-click-modal="false"
 | 
			
		||||
          title="请输入口令继续访问"
 | 
			
		||||
      >
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-input v-model="token" placeholder="在此输入口令">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon class="el-input__icon">
 | 
			
		||||
                <Lock/>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
          <el-button type="primary" @click="submitToken">提交</el-button>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
      </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -54,34 +79,63 @@ import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import {randString} from "@/utils/libs";
 | 
			
		||||
import {ElMessage, ElMessageBox} from 'element-plus'
 | 
			
		||||
import {Tools} from '@element-plus/icons-vue'
 | 
			
		||||
import {Tools, Lock} from '@element-plus/icons-vue'
 | 
			
		||||
import ConfigDialog from '@/components/ConfigDialog.vue'
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {getSessionId, setSessionId} from "@/utils/storage";
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "XChat",
 | 
			
		||||
  components: {ChatPrompt, ChatReply, Tools, ConfigDialog},
 | 
			
		||||
  components: {ChatPrompt, ChatReply, Tools, Lock, ConfigDialog},
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      title: "ChatGPT 控制台",
 | 
			
		||||
      title: 'ChatGPT 控制台',
 | 
			
		||||
      logo: 'images/logo.png',
 | 
			
		||||
      chatData: [],
 | 
			
		||||
      inputValue: '',
 | 
			
		||||
      chatBoxHeight: 0,
 | 
			
		||||
      showDialog: false,
 | 
			
		||||
      showConnectDialog: false,
 | 
			
		||||
      showLoginDialog: false,
 | 
			
		||||
      token: '',
 | 
			
		||||
 | 
			
		||||
      connectingMessageBox: null,
 | 
			
		||||
      socket: null,
 | 
			
		||||
      sending: false
 | 
			
		||||
      toolBoxHeight: 61 + 42,
 | 
			
		||||
      sending: false,
 | 
			
		||||
      loading: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  computed: {},
 | 
			
		||||
 | 
			
		||||
  mounted: function () {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
      this.chatBoxHeight = window.innerHeight - 61;
 | 
			
		||||
      this.chatBoxHeight = window.innerHeight - this.toolBoxHeight;
 | 
			
		||||
    })
 | 
			
		||||
    this.connect();
 | 
			
		||||
 | 
			
		||||
    // 获取会话
 | 
			
		||||
    httpPost("/api/session/get").then(() => {
 | 
			
		||||
      this.connect();
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
      this.showLoginDialog = true;
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < 10; i++) {
 | 
			
		||||
      this.chatData.push({
 | 
			
		||||
        type: "prompt",
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: 'images/user-icon.png',
 | 
			
		||||
        content: "孙悟空为什么可以把金棍棒放进耳朵?",
 | 
			
		||||
      });
 | 
			
		||||
      this.chatData.push({
 | 
			
		||||
        type: "reply",
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: 'images/gpt-icon.png',
 | 
			
		||||
        content: "孙悟空是中国神话中的人物,传说中他可以把金箍棒放进耳朵里,这是一种超自然能力,无法用现代科学解释。这种能力可能是象征孙悟空超人力量的古代文化传说。",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.addEventListener("resize", () => {
 | 
			
		||||
      this.chatBoxHeight = window.innerHeight - this.toolBoxHeight;
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
@@ -91,7 +145,8 @@ export default defineComponent({
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 初始化 WebSocket 对象
 | 
			
		||||
      const socket = new WebSocket(process.env.VUE_APP_WS_HOST + '/api/chat');
 | 
			
		||||
      const token = getSessionId();
 | 
			
		||||
      const socket = new WebSocket('ws://' + process.env.VUE_APP_API_HOST + '/api/chat', [token]);
 | 
			
		||||
      socket.addEventListener('open', () => {
 | 
			
		||||
        ElMessage.success('创建会话成功!');
 | 
			
		||||
 | 
			
		||||
@@ -122,6 +177,9 @@ export default defineComponent({
 | 
			
		||||
              let content = data.content;
 | 
			
		||||
              // 替换换行符
 | 
			
		||||
              if (content.indexOf("\n\n") >= 0) {
 | 
			
		||||
                if (this.chatData[this.chatData.length - 1]["content"].length === 0) {
 | 
			
		||||
                  return
 | 
			
		||||
                }
 | 
			
		||||
                content = content.replace("\n\n", "<br />");
 | 
			
		||||
              }
 | 
			
		||||
              this.chatData[this.chatData.length - 1]["content"] += content;
 | 
			
		||||
@@ -182,7 +240,6 @@ export default defineComponent({
 | 
			
		||||
        content: this.inputValue
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // TODO: 使用 websocket 提交数据到后端
 | 
			
		||||
      this.sending = true;
 | 
			
		||||
      this.socket.send(this.inputValue);
 | 
			
		||||
      this.$refs["text-input"].blur();
 | 
			
		||||
@@ -199,6 +256,26 @@ export default defineComponent({
 | 
			
		||||
      }, 200)
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 提交 Token
 | 
			
		||||
    submitToken: function () {
 | 
			
		||||
      this.showLoginDialog = false;
 | 
			
		||||
      this.loading = true
 | 
			
		||||
 | 
			
		||||
      // 获取会话
 | 
			
		||||
      httpPost("/api/login", {
 | 
			
		||||
        token: this.token
 | 
			
		||||
      }).then((res) => {
 | 
			
		||||
        setSessionId(res.data)
 | 
			
		||||
        this.connect();
 | 
			
		||||
        this.loading = false;
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
        ElMessage.error("口令错误");
 | 
			
		||||
        this.token = '';
 | 
			
		||||
        this.showLoginDialog = true;
 | 
			
		||||
        this.loading = false;
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
@@ -211,7 +288,7 @@ export default defineComponent({
 | 
			
		||||
  .body {
 | 
			
		||||
    background-color: rgba(247, 247, 248, 1);
 | 
			
		||||
    display flex;
 | 
			
		||||
    justify-content center;
 | 
			
		||||
    //justify-content center;
 | 
			
		||||
    align-items flex-start;
 | 
			
		||||
    height 100%;
 | 
			
		||||
 | 
			
		||||
@@ -219,13 +296,24 @@ export default defineComponent({
 | 
			
		||||
      overflow auto;
 | 
			
		||||
      width 100%;
 | 
			
		||||
 | 
			
		||||
      .tool-box {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        display flex;
 | 
			
		||||
        justify-content center;
 | 
			
		||||
        align-items center;
 | 
			
		||||
 | 
			
		||||
        .el-image {
 | 
			
		||||
          margin-right 5px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .chat-box {
 | 
			
		||||
        // 变量定义
 | 
			
		||||
        --content-font-size: 16px;
 | 
			
		||||
        --content-color: #374151;
 | 
			
		||||
 | 
			
		||||
        font-family 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
			
		||||
        padding: 20px 10px;
 | 
			
		||||
        padding: 0 10px 10px 10px;
 | 
			
		||||
 | 
			
		||||
        .chat-line {
 | 
			
		||||
          padding 10px;
 | 
			
		||||
@@ -304,10 +392,27 @@ export default defineComponent({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-message {
 | 
			
		||||
  width 90%;
 | 
			
		||||
  min-width: 300px;
 | 
			
		||||
  min-width: 100px;
 | 
			
		||||
  max-width 600px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.token-dialog {
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
    --el-dialog-width 90%;
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
      padding 10px 10px 20px 10px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-row {
 | 
			
		||||
      flex-wrap nowrap
 | 
			
		||||
 | 
			
		||||
      button {
 | 
			
		||||
        margin-left 5px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user