mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	完成聊天 websocket API 对接
This commit is contained in:
		@@ -1,11 +1,34 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s *Server) Chat(c *gin.Context) {
 | 
			
		||||
	c.JSON(http.StatusOK, gin.H{"code": 0, "message": fmt.Sprintf("HELLO, ChatGPT !!!")})
 | 
			
		||||
	ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	logger.Infof("New websocket connected, IP: %s", c.Request.RemoteAddr)
 | 
			
		||||
	client := NewWsClient(ws)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			_, message, err := client.Receive()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
				client.Close()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TODO: 接受消息,调用 ChatGPT 返回消息
 | 
			
		||||
			logger.Info(string(message))
 | 
			
		||||
			err = client.Send(message)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,22 +14,18 @@ type Client interface {
 | 
			
		||||
 | 
			
		||||
// WsClient websocket client
 | 
			
		||||
type WsClient struct {
 | 
			
		||||
	NodeId    string
 | 
			
		||||
	SessionId string
 | 
			
		||||
	Conn      *websocket.Conn
 | 
			
		||||
	lock      sync.Mutex
 | 
			
		||||
	mt        int
 | 
			
		||||
	closed    bool
 | 
			
		||||
	Conn   *websocket.Conn
 | 
			
		||||
	lock   sync.Mutex
 | 
			
		||||
	mt     int
 | 
			
		||||
	closed bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWsClient(nodeId string, sessionId string, conn *websocket.Conn) *WsClient {
 | 
			
		||||
func NewWsClient(conn *websocket.Conn) *WsClient {
 | 
			
		||||
	return &WsClient{
 | 
			
		||||
		NodeId:    nodeId,
 | 
			
		||||
		SessionId: sessionId,
 | 
			
		||||
		Conn:      conn,
 | 
			
		||||
		lock:      sync.Mutex{},
 | 
			
		||||
		mt:        2, // fixed bug for 'Invalid UTF-8 in text frame'
 | 
			
		||||
		closed:    false,
 | 
			
		||||
		Conn:   conn,
 | 
			
		||||
		lock:   sync.Mutex{},
 | 
			
		||||
		mt:     2, // fixed bug for 'Invalid UTF-8 in text frame'
 | 
			
		||||
		closed: false,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								web/src/assets/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								web/src/assets/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -6,6 +6,7 @@ import App from './App.vue'
 | 
			
		||||
import Home from './views/Chat.vue'
 | 
			
		||||
import NotFound from './views/404.vue'
 | 
			
		||||
import './utils/prototype'
 | 
			
		||||
import "./assets/css/bootstrap.min.css"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
 
 | 
			
		||||
@@ -1,77 +0,0 @@
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import JSONBigInt from 'json-bigint'
 | 
			
		||||
import qs from 'qs'
 | 
			
		||||
 | 
			
		||||
import { ElMessageBox } from 'element-plus'
 | 
			
		||||
 | 
			
		||||
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/x-www-form-urlencoded'
 | 
			
		||||
axios.defaults.transformResponse = [(data, headers) => {
 | 
			
		||||
  if (headers['content-type'].indexOf('application/json') !== -1) {
 | 
			
		||||
    try {
 | 
			
		||||
      data = JSONBigInt.parse(data)
 | 
			
		||||
    } catch (e) { /* Ignore */ }
 | 
			
		||||
  }
 | 
			
		||||
  return data
 | 
			
		||||
}]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// HTTP拦截器
 | 
			
		||||
axios.interceptors.request.use(
 | 
			
		||||
  config => {
 | 
			
		||||
    // set session-name
 | 
			
		||||
    config.headers['Session-Name'] = "xwebssh-sess-token"
 | 
			
		||||
    return config
 | 
			
		||||
  }, error => {
 | 
			
		||||
    return Promise.reject(error)
 | 
			
		||||
  })
 | 
			
		||||
axios.interceptors.response.use(
 | 
			
		||||
  response => {
 | 
			
		||||
    if (response.data.code == 0) {
 | 
			
		||||
      return response
 | 
			
		||||
    } else {
 | 
			
		||||
      return Promise.reject(response.data)
 | 
			
		||||
    }
 | 
			
		||||
  }, error => {
 | 
			
		||||
    if (error.response.status === 401) {
 | 
			
		||||
      ElMessageBox.alert('您未登录或者登录已退出,请先登录再操作。', '登录提醒', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        callback: () => {
 | 
			
		||||
          // TODO: goto login page
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (error.response.status === 400) {
 | 
			
		||||
      return Promise.reject(error.response.data)
 | 
			
		||||
    }
 | 
			
		||||
    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, qs.stringify(data), options).then(response => {
 | 
			
		||||
      resolve(response.data)
 | 
			
		||||
    }).catch(err => {
 | 
			
		||||
      reject(err)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
/* eslint-disable no-constant-condition */
 | 
			
		||||
/**
 | 
			
		||||
 * storage handler
 | 
			
		||||
 */
 | 
			
		||||
// import Storage from 'good-storage'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -16,13 +16,17 @@
 | 
			
		||||
    <div class="input-box" :style="{width: inputBoxWidth+'px'}" id="input-box">
 | 
			
		||||
      <div class="input-container">
 | 
			
		||||
        <textarea class="input-text" id="input-text" rows="1" :style="{minHeight:'24px', height: textHeight+'px'}"
 | 
			
		||||
                  v-on:keyup="inputKeyUp"
 | 
			
		||||
                  v-on:keydown="inputKeyDown"
 | 
			
		||||
                  v-model="inputValue"
 | 
			
		||||
                  placeholder="Input any thing here..."
 | 
			
		||||
                  autofocus></textarea>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="btn-container">
 | 
			
		||||
        <button type="button">发送</button>
 | 
			
		||||
        <button type="button"
 | 
			
		||||
                class="btn btn-success"
 | 
			
		||||
                :disabled="sending"
 | 
			
		||||
                v-on:click="sendMessage">发送
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -60,7 +64,10 @@ export default defineComponent({
 | 
			
		||||
      textHeight: 24,
 | 
			
		||||
      textWidth: 0,
 | 
			
		||||
      chatBoxHeight: 0,
 | 
			
		||||
      isMobile: false
 | 
			
		||||
      isMobile: false,
 | 
			
		||||
 | 
			
		||||
      socket: null,
 | 
			
		||||
      sending: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@@ -81,6 +88,37 @@ export default defineComponent({
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.addEventListener('resize', this.windowResize);
 | 
			
		||||
 | 
			
		||||
    // 初始化 WebSocket 对象
 | 
			
		||||
    const socket = new WebSocket('ws://172.22.11.200:5678/api/chat');
 | 
			
		||||
    socket.addEventListener('open', () => {
 | 
			
		||||
      console.log('WebSocket 连接已打开');
 | 
			
		||||
    });
 | 
			
		||||
    socket.addEventListener('message', event => {
 | 
			
		||||
      if (event.data instanceof Blob) {
 | 
			
		||||
        const reader = new FileReader();
 | 
			
		||||
        reader.readAsText(event.data, "UTF-8");
 | 
			
		||||
        reader.onload = () => {
 | 
			
		||||
          this.chatData.push({
 | 
			
		||||
            type: "reply",
 | 
			
		||||
            id: randString(32),
 | 
			
		||||
            icon: 'images/gpt-icon.png',
 | 
			
		||||
            content: reader.result
 | 
			
		||||
          });
 | 
			
		||||
          this.sending = false;
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
    socket.addEventListener('close', event => {
 | 
			
		||||
      console.log('WebSocket 连接已关闭', event.reason);
 | 
			
		||||
    });
 | 
			
		||||
    socket.addEventListener('error', event => {
 | 
			
		||||
      console.error('WebSocket 连接发生错误', event);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.socket = socket;
 | 
			
		||||
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
@@ -88,22 +126,38 @@ export default defineComponent({
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  methods: {
 | 
			
		||||
    inputKeyUp: function (e) {
 | 
			
		||||
    inputKeyDown: function (e) {
 | 
			
		||||
      // PC 端按回车键直接提交数据
 | 
			
		||||
      if (e.keyCode === 13 && !this.isMobile) {
 | 
			
		||||
        this.chatData.push({
 | 
			
		||||
          type: "prompt",
 | 
			
		||||
          id: randString(32),
 | 
			
		||||
          icon: 'images/user-icon.png',
 | 
			
		||||
          content: this.inputValue
 | 
			
		||||
        });
 | 
			
		||||
        this.inputValue = '';
 | 
			
		||||
        console.log("提交数据")
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        return this.sendMessage();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.inputResize();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 发送消息
 | 
			
		||||
    sendMessage: function () {
 | 
			
		||||
      if (this.sending) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 追加消息
 | 
			
		||||
      this.chatData.push({
 | 
			
		||||
        type: "prompt",
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: 'images/user-icon.png',
 | 
			
		||||
        content: this.inputValue
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // TODO: 使用 websocket 提交数据到后端
 | 
			
		||||
      this.sending = true;
 | 
			
		||||
      this.socket.send(this.inputValue);
 | 
			
		||||
      this.inputValue = '';
 | 
			
		||||
      return true;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 初始化
 | 
			
		||||
    inputResize: function () {
 | 
			
		||||
      // 根据输入的字数自动调整输入框的大小
 | 
			
		||||
@@ -205,21 +259,6 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
    button {
 | 
			
		||||
      width 70px;
 | 
			
		||||
      outline none
 | 
			
		||||
      border none
 | 
			
		||||
      cursor pointer
 | 
			
		||||
      background-color: #07c160
 | 
			
		||||
      position: relative;
 | 
			
		||||
      margin-left: auto;
 | 
			
		||||
      margin-right: auto;
 | 
			
		||||
      font-size 16px
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      text-decoration: none;
 | 
			
		||||
      color: #fff;
 | 
			
		||||
      line-height: 2.25;
 | 
			
		||||
      border-radius: 8px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user