2024-10-26 11:34:55 +08:00
|
|
|
|
// fayApp.js
|
|
|
|
|
class FayInterface {
|
|
|
|
|
constructor(baseWsUrl, baseApiUrl, vueInstance) {
|
|
|
|
|
this.baseWsUrl = baseWsUrl;
|
|
|
|
|
this.baseApiUrl = baseApiUrl;
|
|
|
|
|
this.websocket = null;
|
|
|
|
|
this.vueInstance = vueInstance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connectWebSocket() {
|
|
|
|
|
if (this.websocket) {
|
|
|
|
|
this.websocket.onopen = null;
|
|
|
|
|
this.websocket.onmessage = null;
|
|
|
|
|
this.websocket.onclose = null;
|
|
|
|
|
this.websocket.onerror = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.websocket = new WebSocket(this.baseWsUrl);
|
|
|
|
|
|
|
|
|
|
this.websocket.onopen = () => {
|
|
|
|
|
console.log('WebSocket connection opened');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.websocket.onmessage = (event) => {
|
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
|
this.handleIncomingMessage(data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.websocket.onclose = () => {
|
|
|
|
|
console.log('WebSocket connection closed. Attempting to reconnect...');
|
|
|
|
|
setTimeout(() => this.connectWebSocket(), 5000);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.websocket.onerror = (error) => {
|
|
|
|
|
console.error('WebSocket error:', error);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fetchData(url, options = {}) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching data:', error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVoiceList() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/get-voice-list`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAudioDeviceList() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/get-audio-device-list`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
submitConfig(config) {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/submit`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ config })
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
controlEyes(state) {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/control-eyes`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ state })
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startLive() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/start-live`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopLive() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/stop-live`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-30 19:11:15 +08:00
|
|
|
|
getRunStatus() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/get_run_status`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 11:34:55 +08:00
|
|
|
|
getMessageHistory(username) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const url = `${this.baseApiUrl}/api/get-msg`;
|
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
xhr.open("POST", url);
|
|
|
|
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
|
|
|
const send_data = `data=${encodeURIComponent(JSON.stringify({ username }))}`;
|
|
|
|
|
xhr.send(send_data);
|
|
|
|
|
|
|
|
|
|
xhr.onreadystatechange = function () {
|
|
|
|
|
if (xhr.readyState === 4) {
|
|
|
|
|
if (xhr.status === 200) {
|
|
|
|
|
try {
|
|
|
|
|
const data = JSON.parse(xhr.responseText);
|
|
|
|
|
if (data && data.list) {
|
|
|
|
|
const combinedList = data.list.flat();
|
|
|
|
|
resolve(combinedList);
|
|
|
|
|
} else {
|
|
|
|
|
resolve([]);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Error parsing response:', e);
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
reject(new Error(`Request failed with status ${xhr.status}`));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getUserList() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/get-member-list`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 18:45:44 +08:00
|
|
|
|
getData() {
|
|
|
|
|
return this.fetchData(`${this.baseApiUrl}/api/get-data`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-30 19:11:15 +08:00
|
|
|
|
getTime(){
|
|
|
|
|
const date = new Date();
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 月份从0开始,需要+1
|
|
|
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
|
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
|
|
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
|
|
|
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
|
|
|
const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
|
|
|
|
|
const currentDateTimeWithMs = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
|
|
|
return currentDateTimeWithMs
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 11:34:55 +08:00
|
|
|
|
handleIncomingMessage(data) {
|
|
|
|
|
const vueInstance = this.vueInstance;
|
|
|
|
|
// console.log('Incoming message:', data);
|
|
|
|
|
if (data.liveState !== undefined) {
|
|
|
|
|
vueInstance.liveState = data.liveState;
|
|
|
|
|
if (data.liveState === 1) {
|
|
|
|
|
vueInstance.configEditable = false;
|
|
|
|
|
} else if (data.liveState === 0) {
|
|
|
|
|
vueInstance.configEditable = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.voiceList !== undefined) {
|
|
|
|
|
vueInstance.voiceList = data.voiceList.map(voice => ({
|
|
|
|
|
value: voice.id,
|
|
|
|
|
label: voice.name
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.deviceList !== undefined) {
|
|
|
|
|
vueInstance.deviceList = data.deviceList.map(device => ({
|
|
|
|
|
value: device,
|
|
|
|
|
label: device
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.panelMsg !== undefined) {
|
|
|
|
|
vueInstance.panelMsg = data.panelMsg;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (data.robot) {
|
|
|
|
|
console.log(data.robot)
|
|
|
|
|
vueInstance.$set(vueInstance, 'robot', data.robot);
|
|
|
|
|
}
|
|
|
|
|
if (data.panelReply !== undefined) {
|
|
|
|
|
vueInstance.panelReply = data.panelReply.content;
|
|
|
|
|
const userExists = vueInstance.userList.some(user => user[1] === data.panelReply.username);
|
|
|
|
|
if (!userExists) {
|
|
|
|
|
vueInstance.userList.push([data.panelReply.uid, data.panelReply.username]);
|
|
|
|
|
}
|
|
|
|
|
if (vueInstance.selectedUser && data.panelReply.username === vueInstance.selectedUser[1]) {
|
|
|
|
|
vueInstance.messages.push({
|
2024-11-20 23:44:47 +08:00
|
|
|
|
id: data.panelReply.id,
|
2024-10-26 11:34:55 +08:00
|
|
|
|
username: data.panelReply.username,
|
|
|
|
|
content: data.panelReply.content,
|
|
|
|
|
type: data.panelReply.type,
|
2024-11-06 18:42:52 +08:00
|
|
|
|
timetext: this.getTime(),
|
|
|
|
|
is_adopted:0
|
2024-10-26 11:34:55 +08:00
|
|
|
|
});
|
|
|
|
|
vueInstance.$nextTick(() => {
|
|
|
|
|
const chatContainer = vueInstance.$el.querySelector('.chatmessage');
|
|
|
|
|
if (chatContainer) {
|
|
|
|
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.is_connect !== undefined) {
|
|
|
|
|
vueInstance.isConnected = data.is_connect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.remote_audio_connect !== undefined) {
|
|
|
|
|
vueInstance.remoteAudioConnected = data.remote_audio_connect;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new Vue({
|
|
|
|
|
el: '#app',
|
|
|
|
|
delimiters: ["[[", "]]"],
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
messages: [],
|
|
|
|
|
newMessage: '',
|
|
|
|
|
fayService: null,
|
|
|
|
|
liveState: 0,
|
|
|
|
|
isConnected: false,
|
|
|
|
|
remoteAudioConnected: false,
|
|
|
|
|
userList: [],
|
|
|
|
|
selectedUser: null,
|
|
|
|
|
loading: false,
|
|
|
|
|
chatMessages: {},
|
|
|
|
|
panelMsg: '',
|
|
|
|
|
panelReply: '',
|
2024-10-30 19:11:15 +08:00
|
|
|
|
robot:'static/images/Normal.gif',
|
2024-11-06 18:45:44 +08:00
|
|
|
|
base_url: 'http://127.0.0.1:5000',
|
|
|
|
|
play_sound_enabled: false,
|
|
|
|
|
source_record_enabled: false
|
2024-10-26 11:34:55 +08:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
created() {
|
2024-11-06 18:45:44 +08:00
|
|
|
|
this.initFayService();
|
|
|
|
|
this.getData();
|
2024-10-30 19:11:15 +08:00
|
|
|
|
// this.loadUserList();
|
2024-10-26 11:34:55 +08:00
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
initFayService() {
|
2024-10-30 19:11:15 +08:00
|
|
|
|
this.fayService = new FayInterface('ws://127.0.0.1:10003', this.base_url, this);
|
2024-10-26 11:34:55 +08:00
|
|
|
|
this.fayService.connectWebSocket();
|
2024-10-30 19:11:15 +08:00
|
|
|
|
this.fayService.websocket.addEventListener('open', () => {
|
|
|
|
|
this.loadUserList();
|
|
|
|
|
});
|
2024-10-26 11:34:55 +08:00
|
|
|
|
},
|
|
|
|
|
sendMessage() {
|
|
|
|
|
let _this = this;
|
|
|
|
|
let text = _this.newMessage;
|
|
|
|
|
if (!text) {
|
|
|
|
|
alert('请输入内容');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (_this.selectedUser === 'others' && !_this.othersUser) {
|
|
|
|
|
alert('请输入自定义用户名');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this.liveState != 1) {
|
|
|
|
|
alert('请先开启服务');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let usernameToSend = _this.selectedUser === 'others' ? _this.othersUser : _this.selectedUser[1];
|
|
|
|
|
|
|
|
|
|
this.timer = setTimeout(() => {
|
|
|
|
|
let height = document.querySelector('.chatmessage').scrollHeight;
|
|
|
|
|
document.querySelector('.chatmessage').scrollTop = height;
|
|
|
|
|
}, 1000);
|
|
|
|
|
_this.newMessage = '';
|
2024-10-30 19:11:15 +08:00
|
|
|
|
let url = `${this.base_url}/api/send`;
|
2024-10-26 11:34:55 +08:00
|
|
|
|
let send_data = {
|
|
|
|
|
"msg": text,
|
|
|
|
|
"username": usernameToSend
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let xhr = new XMLHttpRequest();
|
|
|
|
|
xhr.open("post", url);
|
|
|
|
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
|
|
|
xhr.send('data=' + encodeURIComponent(JSON.stringify(send_data)));
|
|
|
|
|
let executed = false;
|
|
|
|
|
xhr.onreadystatechange = async function () {
|
|
|
|
|
if (!executed && xhr.status === 200) {
|
|
|
|
|
executed = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
2024-11-06 18:45:44 +08:00
|
|
|
|
getData() {
|
|
|
|
|
this.fayService.getRunStatus().then((data) => {
|
|
|
|
|
if (data) {
|
|
|
|
|
if(data.status){
|
|
|
|
|
this.liveState = 1;
|
|
|
|
|
this.configEditable = false;
|
|
|
|
|
}else{
|
|
|
|
|
this.liveState = 0;
|
|
|
|
|
this.configEditable = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.fayService.getData().then((data) => {
|
|
|
|
|
if (data) {
|
|
|
|
|
this.updateConfigFromData(data.config);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
updateConfigFromData(config) {
|
|
|
|
|
|
|
|
|
|
if (config.interact) {
|
|
|
|
|
this.play_sound_enabled = config.interact.playSound;
|
|
|
|
|
}
|
|
|
|
|
if (config.source && config.source.record) {
|
|
|
|
|
this.source_record_enabled = config.source.record.enabled;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
saveConfig() {
|
|
|
|
|
let url = `${this.base_url}/api/submit`;
|
|
|
|
|
let send_data = {
|
|
|
|
|
"config": {
|
|
|
|
|
"source": {
|
|
|
|
|
"record": {
|
|
|
|
|
"enabled": this.source_record_enabled,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"interact": {
|
|
|
|
|
"playSound": this.play_sound_enabled,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let xhr = new XMLHttpRequest()
|
|
|
|
|
xhr.open("post", url)
|
|
|
|
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
|
|
|
|
xhr.send('data=' + JSON.stringify(send_data))
|
|
|
|
|
let executed = false
|
|
|
|
|
xhr.onreadystatechange = async function () {
|
|
|
|
|
if (!executed && xhr.status === 200) {
|
|
|
|
|
try {
|
|
|
|
|
let data = await eval('(' + xhr.responseText + ')')
|
|
|
|
|
executed = true
|
|
|
|
|
} catch (e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
changeRecord(){
|
|
|
|
|
if(this.source_record_enabled){
|
|
|
|
|
this.source_record_enabled = false
|
|
|
|
|
}else{
|
|
|
|
|
this.source_record_enabled = true
|
|
|
|
|
}
|
|
|
|
|
this.saveConfig()
|
|
|
|
|
},
|
|
|
|
|
changeSound(){
|
|
|
|
|
if(this.play_sound_enabled){
|
|
|
|
|
this.play_sound_enabled = false
|
|
|
|
|
}else{
|
|
|
|
|
this.play_sound_enabled = true
|
|
|
|
|
}
|
|
|
|
|
this.saveConfig()
|
|
|
|
|
},
|
2024-10-26 11:34:55 +08:00
|
|
|
|
loadUserList() {
|
|
|
|
|
this.fayService.getUserList().then((response) => {
|
|
|
|
|
if (response && response.list) {
|
2024-10-28 12:25:44 +08:00
|
|
|
|
if (response.list.length == 0){
|
|
|
|
|
info = [];
|
|
|
|
|
info[0] = 1;
|
|
|
|
|
info[1] = 'User';
|
|
|
|
|
this.userList.push(info)
|
|
|
|
|
this.selectUser(info);
|
|
|
|
|
}else{
|
2024-10-26 11:34:55 +08:00
|
|
|
|
this.userList = response.list;
|
2024-10-28 12:25:44 +08:00
|
|
|
|
this.selectUser(this.userList[0]);
|
2024-10-26 11:34:55 +08:00
|
|
|
|
}
|
2024-10-28 12:25:44 +08:00
|
|
|
|
}
|
2024-10-26 11:34:55 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
selectUser(user) {
|
|
|
|
|
this.selectedUser = user;
|
|
|
|
|
this.fayService.websocket.send(JSON.stringify({ "Username": user[1] }));
|
2024-11-06 18:42:52 +08:00
|
|
|
|
this.loadMessageHistory(user[1], 'common');
|
2024-10-26 11:34:55 +08:00
|
|
|
|
},
|
|
|
|
|
startLive() {
|
|
|
|
|
this.liveState = 2
|
|
|
|
|
this.fayService.startLive().then(() => {
|
2024-10-30 19:11:15 +08:00
|
|
|
|
this.sendSuccessMsg('已开启!');
|
2024-11-06 18:45:44 +08:00
|
|
|
|
this.getData();
|
2024-10-26 11:34:55 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
stopLive() {
|
|
|
|
|
this.fayService.stopLive().then(() => {
|
|
|
|
|
this.liveState = 3
|
2024-10-30 19:11:15 +08:00
|
|
|
|
this.sendSuccessMsg('已关闭!');
|
2024-10-26 11:34:55 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2024-11-06 18:42:52 +08:00
|
|
|
|
loadMessageHistory(username, type) {
|
2024-10-26 11:34:55 +08:00
|
|
|
|
this.fayService.getMessageHistory(username).then((response) => {
|
|
|
|
|
if (response) {
|
|
|
|
|
this.messages = response;
|
2024-11-06 18:42:52 +08:00
|
|
|
|
if(type == 'common'){
|
2024-10-26 11:34:55 +08:00
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
const chatContainer = this.$el.querySelector('.chatmessage');
|
|
|
|
|
if (chatContainer) {
|
|
|
|
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-11-06 18:42:52 +08:00
|
|
|
|
}
|
2024-10-26 11:34:55 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
sendSuccessMsg(message) {
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '成功',
|
|
|
|
|
message,
|
|
|
|
|
type: 'success',
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-06 18:42:52 +08:00
|
|
|
|
} ,
|
|
|
|
|
adoptText(id) {
|
|
|
|
|
// 调用采纳接口
|
|
|
|
|
this.fayService.fetchData(`${this.base_url}/api/adopt_msg`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ id }) // 发送采纳请求
|
|
|
|
|
})
|
|
|
|
|
.then((response) => {
|
|
|
|
|
if (response && response.status === 'success') {
|
|
|
|
|
// 处理成功的响应
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '成功',
|
|
|
|
|
message: response.msg, // 显示成功消息
|
|
|
|
|
type: 'success',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.loadMessageHistory(this.selectedUser[1], 'adopt');
|
|
|
|
|
} else {
|
|
|
|
|
// 处理失败的响应
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '失败',
|
|
|
|
|
message: response ? response.msg : '请求失败',
|
|
|
|
|
type: 'error',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
2024-11-06 18:45:44 +08:00
|
|
|
|
.catch((error) => {
|
|
|
|
|
// 处理网络错误或HTTP错误
|
|
|
|
|
this.$notify({
|
|
|
|
|
title: '错误',
|
|
|
|
|
message: error.message || '请求失败',
|
|
|
|
|
type: 'error',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
,
|
2024-10-26 11:34:55 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|