完成聊天 websocket API 对接

This commit is contained in:
RockYang 2023-03-17 15:38:05 +08:00
parent c25cc97450
commit 9477b96629
7 changed files with 108 additions and 126 deletions

View File

@ -1,11 +1,34 @@
package server package server
import ( import (
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"net/http" "net/http"
) )
func (s *Server) Chat(c *gin.Context) { 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)
}
}
}()
} }

View File

@ -14,22 +14,18 @@ type Client interface {
// WsClient websocket client // WsClient websocket client
type WsClient struct { type WsClient struct {
NodeId string Conn *websocket.Conn
SessionId string lock sync.Mutex
Conn *websocket.Conn mt int
lock sync.Mutex closed bool
mt int
closed bool
} }
func NewWsClient(nodeId string, sessionId string, conn *websocket.Conn) *WsClient { func NewWsClient(conn *websocket.Conn) *WsClient {
return &WsClient{ return &WsClient{
NodeId: nodeId, Conn: conn,
SessionId: sessionId, lock: sync.Mutex{},
Conn: conn, mt: 2, // fixed bug for 'Invalid UTF-8 in text frame'
lock: sync.Mutex{}, closed: false,
mt: 2, // fixed bug for 'Invalid UTF-8 in text frame'
closed: false,
} }
} }

7
web/src/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@ import App from './App.vue'
import Home from './views/Chat.vue' import Home from './views/Chat.vue'
import NotFound from './views/404.vue' import NotFound from './views/404.vue'
import './utils/prototype' import './utils/prototype'
import "./assets/css/bootstrap.min.css"
const routes = [ const routes = [

View File

@ -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)
})
})
}

View File

@ -1,7 +0,0 @@
/* eslint-disable no-constant-condition */
/**
* storage handler
*/
// import Storage from 'good-storage'

View File

@ -16,13 +16,17 @@
<div class="input-box" :style="{width: inputBoxWidth+'px'}" id="input-box"> <div class="input-box" :style="{width: inputBoxWidth+'px'}" id="input-box">
<div class="input-container"> <div class="input-container">
<textarea class="input-text" id="input-text" rows="1" :style="{minHeight:'24px', height: textHeight+'px'}" <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" v-model="inputValue"
placeholder="Input any thing here..." placeholder="Input any thing here..."
autofocus></textarea> autofocus></textarea>
</div> </div>
<div class="btn-container"> <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> </div>
</div> </div>
@ -60,7 +64,10 @@ export default defineComponent({
textHeight: 24, textHeight: 24,
textWidth: 0, textWidth: 0,
chatBoxHeight: 0, chatBoxHeight: 0,
isMobile: false isMobile: false,
socket: null,
sending: false
} }
}, },
@ -81,6 +88,37 @@ export default defineComponent({
} }
window.addEventListener('resize', this.windowResize); 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() { beforeUnmount() {
@ -88,22 +126,38 @@ export default defineComponent({
}, },
methods: { methods: {
inputKeyUp: function (e) { inputKeyDown: function (e) {
// PC // PC
if (e.keyCode === 13 && !this.isMobile) { if (e.keyCode === 13 && !this.isMobile) {
this.chatData.push({ e.stopPropagation();
type: "prompt", e.preventDefault();
id: randString(32), return this.sendMessage();
icon: 'images/user-icon.png',
content: this.inputValue
});
this.inputValue = '';
console.log("提交数据")
} }
this.inputResize(); 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 () { inputResize: function () {
// //
@ -205,21 +259,6 @@ export default defineComponent({
button { button {
width 70px; 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;
} }
} }