From b9257524c56399502b545f39a23d65ee8a7b646e Mon Sep 17 00:00:00 2001 From: guo zebin Date: Thu, 22 Jan 2026 16:55:34 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=B0=E5=BF=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.暂时屏蔽仿生记忆开关; 2.补充记忆模块的开发文档,以方便ai ide阅读。 --- docs/memory_module.md | 39 ++ gui/static/js/setting.js | 1025 ++++++++++++++++++------------------ gui/templates/setting.html | 18 +- 3 files changed, 559 insertions(+), 523 deletions(-) create mode 100644 docs/memory_module.md diff --git a/docs/memory_module.md b/docs/memory_module.md new file mode 100644 index 0000000..1a60b45 --- /dev/null +++ b/docs/memory_module.md @@ -0,0 +1,39 @@ +# Fay Memory Module + +本说明描述当前默认的认知记忆实现。适用于 `llm/nlp_cognitive_stream.py` + `genagents/modules/memory_stream.py` 这条路径。 + +## 记忆节点 +- 节点类型:`observation`(观察)、`conversation`(对话)、`reflection`(反思) +- 字段: + - `node_id`: 递增整数 + - `node_type`: 节点类型 + - `content`: 记忆内容(纯文本,已去除 `` / ``) + - `importance`: 整数重要度 + - `datetime`: 节点创建时间,格式 `YYYY/MM/DD HH:MM:SS`;旧节点可能为空 + - `created` / `last_retrieved`: 时间步(整型) + - `pointer_id`: 反思节点指向的源节点列表 ID +- 创建: + - 观察:`remember_observation_thread` / `record_observation` + - 对话:`remember_conversation_thread`(问答格式:`{user}:{问}\n{agent}:{答}`,其中 `user/user` 会写成“主人”) + - 反思:`MemoryStream.reflect` 生成 + - 写盘策略:即时不落盘,按定时/退出在 `llm/nlp_cognitive_stream.py::save_agent_memory` 才写入 +- Embedding:`memory_stream._add_node` 创建时生成;维度不符时检索阶段会临时重算(内存中,不立即落盘) + +## 检索与提示词 +- 检索:`MemoryStream.retrieve` 按 recency/relevance/importance 组合权重取回;关联记忆一步检索 `curr_filter="all"` 后按类型分段展示。 +- 展示格式:检索结果中若节点有 `datetime`,前缀 `"[{datetime}] "`;无时间则不展示时间。 +- 关联记忆段落无“关联记忆”标题,直接列出各类型小节(观察、对话、反思)。 + +## 配置与隔离 +- `memory.isolate_by_user`: 打开后记忆目录按用户名隔离。 + +## 运行时要点 +- 文字接口 `no_reply=true` 且有 `observation` 时:只记观察,不回复;无 `messages` 且有 `observation` 会强制 `no_reply=true`。 +- `no_reply=false` 的普通对话:问题/回答会写入对话记忆;`observation` 若存在也会写入观察记忆。 +- Prompt 打印:已关闭(`_log_prompt` 是空操作)。 + +## 文件位置 +- 核心逻辑:`llm/nlp_cognitive_stream.py` +- 记忆结构:`genagents/modules/memory_stream.py` +- 定时保存:`llm/nlp_cognitive_stream.py::save_agent_memory` +- 记忆数据:`memory/memory_stream/nodes.json`, `embeddings.json`, `meta.json` diff --git a/gui/static/js/setting.js b/gui/static/js/setting.js index 32ecab0..8d77ee1 100644 --- a/gui/static/js/setting.js +++ b/gui/static/js/setting.js @@ -1,518 +1,519 @@ -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; - } +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; - this.use_bionic_memory = config.memory.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 - }, + // 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": this.use_bionic_memory + "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(`决策分析页面已启动,请复制以下链接在新窗口中打开:

${data.url}`, '克隆人格', { - 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(); - } - }, - }, -}); + "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(`决策分析页面已启动,请复制以下链接在新窗口中打开:

${data.url}`, '克隆人格', { + 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(); + } + }, + }, +}); diff --git a/gui/templates/setting.html b/gui/templates/setting.html index 47d151c..deca5aa 100644 --- a/gui/templates/setting.html +++ b/gui/templates/setting.html @@ -115,16 +115,12 @@
  •  敏 感 度 :
  • -
  • 认知隔离: - - 开启后每个用户将拥有独立记忆 -
  • -
  • 仿生记忆: - - 开启后使用仿生记忆系统(试验功能,人格克隆不可用) -
  • - - +
  • 认知隔离: + + 开启后每个用户将拥有独立记忆 +
  • + + @@ -175,4 +171,4 @@ - \ No newline at end of file +