Files
Fay/gui/static/js/setting.js
guo zebin b9257524c5 记忆模块
1.暂时屏蔽仿生记忆开关;
2.补充记忆模块的开发文档,以方便ai ide阅读。
2026-01-22 16:55:34 +08:00

520 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
getData() {
return this.fetchData(`${this.baseApiUrl}/api/get-data`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
}
submitConfig(config) {
return this.fetchData(`${this.baseApiUrl}/api/submit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config }),
});
}
startLive() {
return this.fetchData(`${this.baseApiUrl}/api/start-live`, {
method: 'POST',
});
}
stopLive() {
return this.fetchData(`${this.baseApiUrl}/api/stop-live`, {
method: 'POST',
});
}
getRunStatus() {
return this.fetchData(`${this.baseApiUrl}/api/get-run-status`, {
method: 'POST'
});
}
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.robot) {
console.log(data.robot);
vueInstance.$set(vueInstance, 'robot', data.robot);
}
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 {
hostname: window.location.hostname,
base_url: window.location.origin,
messages: [],
newMessage: '',
fayService: null,
liveState: 0,
isConnected: false,
remoteAudioConnected: false,
userList: [],
selectedUser: null,
loading: false,
chatMessages: {},
panelMsg: '',
panelReply: '',
robot: 'images/emoji.png',
configEditable: true,
source_liveRoom_url: '',
play_sound_enabled: false,
mcpOnlineStatus: false,
mcpCheckTimer: null,
visualization_detection_enabled: false,
source_record_enabled: false,
source_record_device: '',
attribute_name: "",
attribute_gender: "",
attribute_age: "",
attribute_birth: "",
attribute_zodiac: "",
attribute_constellation: "",
attribute_job: "",
attribute_additional: "",
attribute_contact: "",
attribute_voice: "",
attribute_position: "",
attribute_goal: "",
QnA:"",
interact_perception_gift: 0,
interact_perception_follow: 0,
interact_perception_join: 0,
interact_perception_chat: 0,
interact_perception_indifferent: 0,
interact_maxInteractTime: 15,
voiceList: [],
deviceList: [],
wake_word_enabled:false,
wake_word: '',
loading: false,
remote_audio_connect: false,
wake_word_type: 'common',
wake_word_type_options: [{
value: 'common',
label: '普通'
}, {
value: 'front',
label: '前置词'
}],
automatic_player_status: false,
automatic_player_url: "",
host_url: window.location.protocol + '//' + window.location.hostname + ':' + window.location.port,
memory_isolate_by_user: false,
use_bionic_memory: false,
};
},
created() {
this.initFayService();
this.getData();
this.checkMcpStatus();
this.startMcpStatusTimer();
},
methods: {
initFayService() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws';
const wsHost = window.location.hostname;
this.fayService = new FayInterface(`${wsProtocol}://${wsHost}:10003`, this.host_url, this);
this.fayService.connectWebSocket();
},
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.voiceList = data.voice_list.map((voice) => ({
value: voice.id,
label: voice.name,
}));
this.updateConfigFromData(data.config);
}
});
},
updateConfigFromData(config) {
if (config.interact) {
this.play_sound_enabled = config.interact.playSound;
this.visualization_detection_enabled = config.interact.visualization;
this.QnA = config.interact.QnA;
}
if (config.source && config.source.record) {
this.source_record_enabled = config.source.record.enabled;
this.source_record_device = config.source.record.device;
this.wake_word = config.source.wake_word;
this.wake_word_type = config.source.wake_word_type;
this.wake_word_enabled = config.source.wake_word_enabled;
this.automatic_player_status = config.source.automatic_player_status;
this.automatic_player_url = config.source.automatic_player_url;
}
if (config.attribute) {
this.attribute_name = config.attribute.name;
this.attribute_gender = config.attribute.gender;
this.attribute_age = config.attribute.age;
this.attribute_name = config.attribute.name;
this.attribute_gender = config.attribute.gender;
this.attribute_birth = config.attribute.birth;
this.attribute_zodiac = config.attribute.zodiac;
this.attribute_constellation = config.attribute.constellation;
this.attribute_job = config.attribute.job;
this.attribute_additional = config.attribute.additional;
this.attribute_contact = config.attribute.contact;
this.attribute_voice = config.attribute.voice;
this.attribute_position = config.attribute.position || "客服";
this.attribute_goal = config.attribute.goal || "解决问题";
}
if (config.interact.perception) {
this.interact_perception_follow = config.interact.perception.follow;
}
if (config.memory) {
this.memory_isolate_by_user = config.memory.isolate_by_user || false;
}
// Bionic memory switch removed from UI; force disabled
this.use_bionic_memory = false;
},
saveConfig() {
let url = `${this.host_url}/api/submit`;
let send_data = {
"config": {
"source": {
"liveRoom": {
"enabled": this.configEditable,
"url": this.source_liveRoom_url
},
"record": {
"enabled": this.source_record_enabled,
"device": this.source_record_device
},
"wake_word_enabled": this.wake_word_enabled,
"wake_word": this.wake_word,
"wake_word_type": this.wake_word_type,
"automatic_player_status": this.automatic_player_status,
"automatic_player_url": this.automatic_player_url
},
"attribute": {
"voice": this.attribute_voice,
"name": this.attribute_name,
"gender": this.attribute_gender,
"age": this.attribute_age,
"birth": this.attribute_birth,
"zodiac": this.attribute_zodiac,
"constellation": this.attribute_constellation,
"job": this.attribute_job,
"additional": this.attribute_additional,
"contact": this.attribute_contact,
"position": this.attribute_position,
"goal": this.attribute_goal,
},
"interact": {
"playSound": this.play_sound_enabled,
"visualization": this.visualization_detection_enabled,
"QnA": this.QnA,
"perception": {
"follow": this.interact_perception_follow
},
"maxInteractTime": this.interact_maxInteractTime
},
"memory": {
"isolate_by_user": this.memory_isolate_by_user,
"use_bionic_memory": false
},
"items": []
}
};
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 + ')')
console.log("data: " + data['result'])
executed = true
} catch (e) {
}
}
}
this.sendSuccessMsg("配置已保存!");
},
startLive() {
this.liveState = 2
this.fayService.startLive().then(() => {
this.configEditable = false;
this.sendSuccessMsg('已开启!');
});
},
stopLive() {
this.liveState = 3
this.fayService.stopLive().then(() => {
this.configEditable = true;
this.sendSuccessMsg('已关闭!');
});
},
sendSuccessMsg(message) {
this.$notify({
title: '成功',
message,
type: 'success',
});
},
clearMemory() {
this.$confirm('清除记忆操作将删除Fay的所有对话记忆清除后需要重启应用才能生效确认继续吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 发送清除记忆请求
fetch(`${this.host_url}/api/clear-memory`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.sendSuccessMsg(data.message || "记忆已清除,请重启应用使更改生效");
} else {
this.$notify({
title: '错误',
message: data.message || '清除记忆失败',
type: 'error'
});
}
})
.catch(error => {
this.$notify({
title: '错误',
message: '清除记忆请求失败',
type: 'error'
});
});
}).catch(() => {
// 用户取消操作
});
},
clonePersonality() {
// 检查是否启用了仿生记忆
if (this.use_bionic_memory) {
this.$notify({
title: '提示',
message: '仿生记忆模式下不支持人格克隆功能,请在设置中关闭仿生记忆后重试',
type: 'warning'
});
return;
}
if (this.liveState === 1) {
this.$prompt('请输入克隆要求', '克隆人格', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: '请输入克隆要求,例如:你现在是一个活泼开朗的助手...'
}).then(({ value }) => {
if (!value) {
this.$notify({
title: '提示',
message: '克隆要求不能为空',
type: 'warning'
});
return;
}
// 直接启动genagents_flask.py并打开decision_interview.html页面
fetch(`${this.host_url}/api/start-genagents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction: value })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 弹出提示,显示克隆地址,不自动打开
this.$alert(`决策分析页面已启动,请复制以下链接在新窗口中打开:<br><br><code style="background-color: #f5f5f5; padding: 5px; border-radius: 3px;">${data.url}</code>`, '克隆人格', {
confirmButtonText: '确定',
dangerouslyUseHTMLString: true
});
} else {
this.$notify({
title: '错误',
message: data.message || '启动决策分析页面失败',
type: 'error'
});
}
})
.catch(error => {
this.$notify({
title: '错误',
message: '启动决策分析页面请求失败',
type: 'error'
});
});
});
} else {
this.$notify({
title: '提示',
message: '请先开Fay后再执行此操作',
type: 'warning'
});
}
},
// 检查MCP服务器状态
checkMcpStatus() {
const mcpUrl = `http://${this.hostname}:5010/api/mcp/servers`;
// 使用超时设置的fetch请求
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3秒超时
fetch(mcpUrl, { signal: controller.signal })
.then(response => {
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error('MCP服务器响应不正常');
}
return response.json();
})
.then(data => {
if (Array.isArray(data)) {
// 检查是否有任何一个MCP服务器在线
const hasOnlineServer = data.some(server => server.status === 'online');
this.mcpOnlineStatus = hasOnlineServer;
} else {
console.warn('MCP服务器返回的数据格式不正确');
this.mcpOnlineStatus = false;
}
})
.catch(error => {
clearTimeout(timeoutId);
// 如果是超时错误,不输出详细错误信息
if (error.name === 'AbortError') {
console.warn('MCP服务器请求超时');
} else {
console.warn('检查MCP状态出错:', error.message);
}
this.mcpOnlineStatus = false;
});
},
// 启动MCP状态检查定时器
startMcpStatusTimer() {
// 清除可能存在的旧定时器
if (this.mcpCheckTimer) {
clearInterval(this.mcpCheckTimer);
}
// 设置新的定时器每30秒检查一次MCP状态
this.mcpCheckTimer = setInterval(() => {
this.checkMcpStatus();
}, 30000);
},
// 仿生记忆开关变化事件处理
onBionicMemoryChange(value) {
if (value) {
this.$confirm('开启仿生记忆后将使用不同的记忆系统,人格克隆功能和认知隔离功能将不可用。确认开启吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 用户确认,保存配置
this.saveConfig();
}).catch(() => {
// 用户取消,恢复开关状态
this.use_bionic_memory = false;
});
} else {
// 关闭仿生记忆,直接保存配置
this.saveConfig();
}
},
},
});