mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 21:57:14 +08:00
feat(dream): add Dream Diary tab to memory management page
- Backend: MemoryService supports category param (memory/dream), lists memory/dreams/*.md - Backend: MemoryContentHandler resolves dream files from memory/dreams/ directory - Frontend: add tab switcher (Memory Files / Dream Diary) matching knowledge tab style - Frontend: dream entries show purple "Dream" badge, empty state with moon icon - Cloud dispatch passes category param for consistency
This commit is contained in:
@@ -32,68 +32,80 @@ class MemoryService:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# list — paginated file metadata
|
# list — paginated file metadata
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def list_files(self, page: int = 1, page_size: int = 20) -> dict:
|
def list_files(self, page: int = 1, page_size: int = 20, category: str = "memory") -> dict:
|
||||||
"""
|
"""
|
||||||
List all memory files with metadata (without content).
|
List memory or dream files with metadata (without content).
|
||||||
|
|
||||||
Returns::
|
Args:
|
||||||
|
category: ``"memory"`` (default) — MEMORY.md + daily files;
|
||||||
{
|
``"dream"`` — dream diary files from memory/dreams/
|
||||||
"page": 1,
|
|
||||||
"page_size": 20,
|
|
||||||
"total": 15,
|
|
||||||
"list": [
|
|
||||||
{"filename": "MEMORY.md", "type": "global", "size": 2048, "updated_at": "2026-02-20 10:00:00"},
|
|
||||||
{"filename": "2026-02-20.md", "type": "daily", "size": 512, "updated_at": "2026-02-20 09:30:00"},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
|
if category == "dream":
|
||||||
|
files = self._list_dream_files()
|
||||||
|
else:
|
||||||
|
files = self._list_memory_files()
|
||||||
|
|
||||||
|
total = len(files)
|
||||||
|
start = (page - 1) * page_size
|
||||||
|
end = start + page_size
|
||||||
|
|
||||||
|
return {
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"total": total,
|
||||||
|
"list": files[start:end],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _list_memory_files(self) -> List[dict]:
|
||||||
|
"""MEMORY.md + memory/*.md (newest first)."""
|
||||||
files: List[dict] = []
|
files: List[dict] = []
|
||||||
|
|
||||||
# 1. Global memory — MEMORY.md in workspace root
|
|
||||||
global_path = os.path.join(self.workspace_root, "MEMORY.md")
|
global_path = os.path.join(self.workspace_root, "MEMORY.md")
|
||||||
if os.path.isfile(global_path):
|
if os.path.isfile(global_path):
|
||||||
files.append(self._file_info(global_path, "MEMORY.md", "global"))
|
files.append(self._file_info(global_path, "MEMORY.md", "global"))
|
||||||
|
|
||||||
# 2. Daily memory files — memory/*.md (sorted newest first)
|
|
||||||
if os.path.isdir(self.memory_dir):
|
if os.path.isdir(self.memory_dir):
|
||||||
daily_files = []
|
daily_files = []
|
||||||
for name in os.listdir(self.memory_dir):
|
for name in os.listdir(self.memory_dir):
|
||||||
full = os.path.join(self.memory_dir, name)
|
full = os.path.join(self.memory_dir, name)
|
||||||
if os.path.isfile(full) and name.endswith(".md"):
|
if os.path.isfile(full) and name.endswith(".md"):
|
||||||
daily_files.append((name, full))
|
daily_files.append((name, full))
|
||||||
# Sort by filename descending (newest date first)
|
|
||||||
daily_files.sort(key=lambda x: x[0], reverse=True)
|
daily_files.sort(key=lambda x: x[0], reverse=True)
|
||||||
for name, full in daily_files:
|
for name, full in daily_files:
|
||||||
files.append(self._file_info(full, name, "daily"))
|
files.append(self._file_info(full, name, "daily"))
|
||||||
|
|
||||||
total = len(files)
|
return files
|
||||||
|
|
||||||
# Paginate
|
def _list_dream_files(self) -> List[dict]:
|
||||||
start = (page - 1) * page_size
|
"""memory/dreams/*.md (newest first)."""
|
||||||
end = start + page_size
|
files: List[dict] = []
|
||||||
page_items = files[start:end]
|
dreams_dir = os.path.join(self.memory_dir, "dreams")
|
||||||
|
|
||||||
return {
|
if os.path.isdir(dreams_dir):
|
||||||
"page": page,
|
entries = []
|
||||||
"page_size": page_size,
|
for name in os.listdir(dreams_dir):
|
||||||
"total": total,
|
full = os.path.join(dreams_dir, name)
|
||||||
"list": page_items,
|
if os.path.isfile(full) and name.endswith(".md"):
|
||||||
}
|
entries.append((name, full))
|
||||||
|
entries.sort(key=lambda x: x[0], reverse=True)
|
||||||
|
for name, full in entries:
|
||||||
|
files.append(self._file_info(full, name, "dream"))
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# content — read a single file
|
# content — read a single file
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def get_content(self, filename: str) -> dict:
|
def get_content(self, filename: str, category: str = "memory") -> dict:
|
||||||
"""
|
"""
|
||||||
Read the full content of a memory file.
|
Read the full content of a memory or dream file.
|
||||||
|
|
||||||
:param filename: File name, e.g. ``MEMORY.md`` or ``2026-02-20.md``
|
:param filename: File name, e.g. ``MEMORY.md``, ``2026-02-20.md``
|
||||||
|
:param category: ``"memory"`` or ``"dream"``
|
||||||
:return: dict with ``filename`` and ``content``
|
:return: dict with ``filename`` and ``content``
|
||||||
:raises FileNotFoundError: if the file does not exist
|
:raises FileNotFoundError: if the file does not exist
|
||||||
"""
|
"""
|
||||||
path = self._resolve_path(filename)
|
path = self._resolve_path(filename, category)
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise FileNotFoundError(f"Memory file not found: {filename}")
|
raise FileNotFoundError(f"Memory file not found: {filename}")
|
||||||
|
|
||||||
@@ -113,7 +125,7 @@ class MemoryService:
|
|||||||
Dispatch a memory management action.
|
Dispatch a memory management action.
|
||||||
|
|
||||||
:param action: ``list`` or ``content``
|
:param action: ``list`` or ``content``
|
||||||
:param payload: action-specific payload
|
:param payload: action-specific payload (supports ``category``: ``"memory"`` | ``"dream"``)
|
||||||
:return: protocol-compatible response dict
|
:return: protocol-compatible response dict
|
||||||
"""
|
"""
|
||||||
payload = payload or {}
|
payload = payload or {}
|
||||||
@@ -121,14 +133,16 @@ class MemoryService:
|
|||||||
if action == "list":
|
if action == "list":
|
||||||
page = payload.get("page", 1)
|
page = payload.get("page", 1)
|
||||||
page_size = payload.get("page_size", 20)
|
page_size = payload.get("page_size", 20)
|
||||||
result_payload = self.list_files(page=page, page_size=page_size)
|
category = payload.get("category", "memory")
|
||||||
|
result_payload = self.list_files(page=page, page_size=page_size, category=category)
|
||||||
return {"action": action, "code": 200, "message": "success", "payload": result_payload}
|
return {"action": action, "code": 200, "message": "success", "payload": result_payload}
|
||||||
|
|
||||||
elif action == "content":
|
elif action == "content":
|
||||||
filename = payload.get("filename")
|
filename = payload.get("filename")
|
||||||
if not filename:
|
if not filename:
|
||||||
return {"action": action, "code": 400, "message": "filename is required", "payload": None}
|
return {"action": action, "code": 400, "message": "filename is required", "payload": None}
|
||||||
result_payload = self.get_content(filename)
|
category = payload.get("category", "memory")
|
||||||
|
result_payload = self.get_content(filename, category=category)
|
||||||
return {"action": action, "code": 200, "message": "success", "payload": result_payload}
|
return {"action": action, "code": 200, "message": "success", "payload": result_payload}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -145,18 +159,20 @@ class MemoryService:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# internal helpers
|
# internal helpers
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def _resolve_path(self, filename: str) -> str:
|
def _resolve_path(self, filename: str, category: str = "memory") -> str:
|
||||||
"""
|
"""
|
||||||
Safely resolve a filename to its absolute path within the allowed directory.
|
Safely resolve a filename to its absolute path within the allowed directory.
|
||||||
|
|
||||||
- ``MEMORY.md`` → ``{workspace_root}/MEMORY.md``
|
- ``MEMORY.md`` → ``{workspace_root}/MEMORY.md``
|
||||||
- ``2026-02-20.md`` → ``{workspace_root}/memory/2026-02-20.md``
|
- ``2026-02-20.md`` (memory) → ``{workspace_root}/memory/2026-02-20.md``
|
||||||
|
- ``2026-02-20.md`` (dream) → ``{workspace_root}/memory/dreams/2026-02-20.md``
|
||||||
|
|
||||||
Raises ValueError if the resolved path escapes the allowed directory
|
Raises ValueError if the resolved path escapes the allowed directory.
|
||||||
(path traversal protection).
|
|
||||||
"""
|
"""
|
||||||
if filename == "MEMORY.md":
|
if filename == "MEMORY.md":
|
||||||
base_dir = self.workspace_root
|
base_dir = self.workspace_root
|
||||||
|
elif category == "dream":
|
||||||
|
base_dir = os.path.join(self.memory_dir, "dreams")
|
||||||
else:
|
else:
|
||||||
base_dir = self.memory_dir
|
base_dir = self.memory_dir
|
||||||
|
|
||||||
|
|||||||
@@ -660,6 +660,16 @@
|
|||||||
<h2 class="text-xl font-bold text-slate-800 dark:text-slate-100" data-i18n="memory_title">记忆管理</h2>
|
<h2 class="text-xl font-bold text-slate-800 dark:text-slate-100" data-i18n="memory_title">记忆管理</h2>
|
||||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1" data-i18n="memory_desc">查看 Agent 记忆文件和内容</p>
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1" data-i18n="memory_desc">查看 Agent 记忆文件和内容</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center bg-slate-100 dark:bg-white/10 rounded-lg p-0.5">
|
||||||
|
<button id="memory-tab-files" onclick="switchMemoryTab('files')"
|
||||||
|
class="memory-tab px-3 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-colors duration-150 active">
|
||||||
|
<i class="fas fa-file-lines mr-1.5"></i><span data-i18n="memory_tab_files">记忆文件</span>
|
||||||
|
</button>
|
||||||
|
<button id="memory-tab-dreams" onclick="switchMemoryTab('dreams')"
|
||||||
|
class="memory-tab px-3 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-colors duration-150">
|
||||||
|
<i class="fas fa-moon mr-1.5"></i><span data-i18n="memory_tab_dreams">梦境日记</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="memory-empty" class="flex flex-col items-center justify-center py-20">
|
<div id="memory-empty" class="flex flex-col items-center justify-center py-20">
|
||||||
<div class="w-16 h-16 rounded-2xl bg-purple-50 dark:bg-purple-900/20 flex items-center justify-center mb-4">
|
<div class="w-16 h-16 rounded-2xl bg-purple-50 dark:bg-purple-900/20 flex items-center justify-center mb-4">
|
||||||
|
|||||||
@@ -890,15 +890,15 @@
|
|||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
/* Tab toggle */
|
/* Tab toggle */
|
||||||
.knowledge-tab {
|
.knowledge-tab, .memory-tab {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
.knowledge-tab.active {
|
.knowledge-tab.active, .memory-tab.active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #334155;
|
color: #334155;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
.dark .knowledge-tab.active {
|
.dark .knowledge-tab.active, .dark .memory-tab.active {
|
||||||
background: rgba(255,255,255,0.1);
|
background: rgba(255,255,255,0.1);
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const I18N = {
|
|||||||
skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用',
|
skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用',
|
||||||
skill_toggle_error: '操作失败,请稍后再试',
|
skill_toggle_error: '操作失败,请稍后再试',
|
||||||
memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容',
|
memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容',
|
||||||
|
memory_tab_files: '记忆文件', memory_tab_dreams: '梦境日记',
|
||||||
memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处',
|
memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处',
|
||||||
memory_back: '返回列表',
|
memory_back: '返回列表',
|
||||||
memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间',
|
memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间',
|
||||||
@@ -139,6 +140,7 @@ const I18N = {
|
|||||||
skills_section_title: 'Skills', skill_enable: 'Enable', skill_disable: 'Disable',
|
skills_section_title: 'Skills', skill_enable: 'Enable', skill_disable: 'Disable',
|
||||||
skill_toggle_error: 'Operation failed, please try again',
|
skill_toggle_error: 'Operation failed, please try again',
|
||||||
memory_title: 'Memory', memory_desc: 'View agent memory files and contents',
|
memory_title: 'Memory', memory_desc: 'View agent memory files and contents',
|
||||||
|
memory_tab_files: 'Memory Files', memory_tab_dreams: 'Dream Diary',
|
||||||
memory_loading: 'Loading memory files...', memory_loading_desc: 'Memory files will be displayed here',
|
memory_loading: 'Loading memory files...', memory_loading_desc: 'Memory files will be displayed here',
|
||||||
memory_back: 'Back to list',
|
memory_back: 'Back to list',
|
||||||
memory_col_name: 'Filename', memory_col_type: 'Type', memory_col_size: 'Size', memory_col_updated: 'Updated',
|
memory_col_name: 'Filename', memory_col_type: 'Type', memory_col_size: 'Size', memory_col_updated: 'Updated',
|
||||||
@@ -2495,12 +2497,20 @@ function toggleSkill(name, currentlyEnabled) {
|
|||||||
// Memory View
|
// Memory View
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
let memoryPage = 1;
|
let memoryPage = 1;
|
||||||
|
let memoryCategory = 'memory'; // 'memory' | 'dream'
|
||||||
const memoryPageSize = 10;
|
const memoryPageSize = 10;
|
||||||
|
|
||||||
|
function switchMemoryTab(tab) {
|
||||||
|
document.querySelectorAll('.memory-tab').forEach(el => el.classList.remove('active'));
|
||||||
|
document.getElementById('memory-tab-' + tab).classList.add('active');
|
||||||
|
memoryCategory = tab === 'dreams' ? 'dream' : 'memory';
|
||||||
|
loadMemoryView(1);
|
||||||
|
}
|
||||||
|
|
||||||
function loadMemoryView(page) {
|
function loadMemoryView(page) {
|
||||||
page = page || 1;
|
page = page || 1;
|
||||||
memoryPage = page;
|
memoryPage = page;
|
||||||
fetch(`/api/memory?page=${page}&page_size=${memoryPageSize}`).then(r => r.json()).then(data => {
|
fetch(`/api/memory?page=${page}&page_size=${memoryPageSize}&category=${memoryCategory}`).then(r => r.json()).then(data => {
|
||||||
if (data.status !== 'success') return;
|
if (data.status !== 'success') return;
|
||||||
const emptyEl = document.getElementById('memory-empty');
|
const emptyEl = document.getElementById('memory-empty');
|
||||||
const listEl = document.getElementById('memory-list');
|
const listEl = document.getElementById('memory-list');
|
||||||
@@ -2508,7 +2518,15 @@ function loadMemoryView(page) {
|
|||||||
const total = data.total || 0;
|
const total = data.total || 0;
|
||||||
|
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
emptyEl.querySelector('p').textContent = currentLang === 'zh' ? '暂无记忆文件' : 'No memory files';
|
const emptyIcon = emptyEl.querySelector('i');
|
||||||
|
const emptyTitle = emptyEl.querySelector('p');
|
||||||
|
if (memoryCategory === 'dream') {
|
||||||
|
emptyIcon.className = 'fas fa-moon text-purple-400 text-xl';
|
||||||
|
emptyTitle.textContent = currentLang === 'zh' ? '暂无梦境日记' : 'No dream diaries yet';
|
||||||
|
} else {
|
||||||
|
emptyIcon.className = 'fas fa-brain text-purple-400 text-xl';
|
||||||
|
emptyTitle.textContent = currentLang === 'zh' ? '暂无记忆文件' : 'No memory files';
|
||||||
|
}
|
||||||
emptyEl.classList.remove('hidden');
|
emptyEl.classList.remove('hidden');
|
||||||
listEl.classList.add('hidden');
|
listEl.classList.add('hidden');
|
||||||
return;
|
return;
|
||||||
@@ -2521,10 +2539,15 @@ function loadMemoryView(page) {
|
|||||||
files.forEach(f => {
|
files.forEach(f => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.className = 'border-b border-slate-100 dark:border-white/5 hover:bg-slate-50 dark:hover:bg-white/5 cursor-pointer transition-colors';
|
tr.className = 'border-b border-slate-100 dark:border-white/5 hover:bg-slate-50 dark:hover:bg-white/5 cursor-pointer transition-colors';
|
||||||
tr.onclick = () => openMemoryFile(f.filename);
|
tr.onclick = () => openMemoryFile(f.filename, memoryCategory);
|
||||||
const typeLabel = f.type === 'global'
|
let typeLabel;
|
||||||
? '<span class="px-2 py-0.5 rounded-full text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400">Global</span>'
|
if (f.type === 'global') {
|
||||||
: '<span class="px-2 py-0.5 rounded-full text-xs bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400">Daily</span>';
|
typeLabel = '<span class="px-2 py-0.5 rounded-full text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400">Global</span>';
|
||||||
|
} else if (f.type === 'dream') {
|
||||||
|
typeLabel = '<span class="px-2 py-0.5 rounded-full text-xs bg-violet-50 dark:bg-violet-900/30 text-violet-600 dark:text-violet-400">Dream</span>';
|
||||||
|
} else {
|
||||||
|
typeLabel = '<span class="px-2 py-0.5 rounded-full text-xs bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400">Daily</span>';
|
||||||
|
}
|
||||||
const sizeStr = f.size < 1024 ? f.size + ' B' : (f.size / 1024).toFixed(1) + ' KB';
|
const sizeStr = f.size < 1024 ? f.size + ' B' : (f.size / 1024).toFixed(1) + ' KB';
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="px-4 py-3 text-sm font-mono text-slate-700 dark:text-slate-200">${escapeHtml(f.filename)}</td>
|
<td class="px-4 py-3 text-sm font-mono text-slate-700 dark:text-slate-200">${escapeHtml(f.filename)}</td>
|
||||||
@@ -2546,8 +2569,9 @@ function loadMemoryView(page) {
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMemoryFile(filename) {
|
function openMemoryFile(filename, category) {
|
||||||
fetch(`/api/memory/content?filename=${encodeURIComponent(filename)}`).then(r => r.json()).then(data => {
|
category = category || 'memory';
|
||||||
|
fetch(`/api/memory/content?filename=${encodeURIComponent(filename)}&category=${category}`).then(r => r.json()).then(data => {
|
||||||
if (data.status !== 'success') return;
|
if (data.status !== 'success') return;
|
||||||
document.getElementById('memory-panel-list').classList.add('hidden');
|
document.getElementById('memory-panel-list').classList.add('hidden');
|
||||||
const panel = document.getElementById('memory-panel-viewer');
|
const panel = document.getElementById('memory-panel-viewer');
|
||||||
@@ -3444,10 +3468,9 @@ navigateTo = function(viewId) {
|
|||||||
if (viewId === 'config') loadConfigView();
|
if (viewId === 'config') loadConfigView();
|
||||||
else if (viewId === 'skills') loadSkillsView();
|
else if (viewId === 'skills') loadSkillsView();
|
||||||
else if (viewId === 'memory') {
|
else if (viewId === 'memory') {
|
||||||
// Always start from the list panel when navigating to memory
|
|
||||||
document.getElementById('memory-panel-viewer').classList.add('hidden');
|
document.getElementById('memory-panel-viewer').classList.add('hidden');
|
||||||
document.getElementById('memory-panel-list').classList.remove('hidden');
|
document.getElementById('memory-panel-list').classList.remove('hidden');
|
||||||
loadMemoryView(1);
|
switchMemoryTab('files');
|
||||||
}
|
}
|
||||||
else if (viewId === 'knowledge') loadKnowledgeView();
|
else if (viewId === 'knowledge') loadKnowledgeView();
|
||||||
else if (viewId === 'channels') loadChannelsView();
|
else if (viewId === 'channels') loadChannelsView();
|
||||||
|
|||||||
@@ -1538,10 +1538,13 @@ class MemoryHandler:
|
|||||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||||
try:
|
try:
|
||||||
from agent.memory.service import MemoryService
|
from agent.memory.service import MemoryService
|
||||||
params = web.input(page='1', page_size='20')
|
params = web.input(page='1', page_size='20', category='memory')
|
||||||
workspace_root = _get_workspace_root()
|
workspace_root = _get_workspace_root()
|
||||||
service = MemoryService(workspace_root)
|
service = MemoryService(workspace_root)
|
||||||
result = service.list_files(page=int(params.page), page_size=int(params.page_size))
|
result = service.list_files(
|
||||||
|
page=int(params.page), page_size=int(params.page_size),
|
||||||
|
category=params.category,
|
||||||
|
)
|
||||||
return json.dumps({"status": "success", **result}, ensure_ascii=False)
|
return json.dumps({"status": "success", **result}, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[WebChannel] Memory API error: {e}")
|
logger.error(f"[WebChannel] Memory API error: {e}")
|
||||||
@@ -1554,12 +1557,12 @@ class MemoryContentHandler:
|
|||||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||||
try:
|
try:
|
||||||
from agent.memory.service import MemoryService
|
from agent.memory.service import MemoryService
|
||||||
params = web.input(filename='')
|
params = web.input(filename='', category='memory')
|
||||||
if not params.filename:
|
if not params.filename:
|
||||||
return json.dumps({"status": "error", "message": "filename required"})
|
return json.dumps({"status": "error", "message": "filename required"})
|
||||||
workspace_root = _get_workspace_root()
|
workspace_root = _get_workspace_root()
|
||||||
service = MemoryService(workspace_root)
|
service = MemoryService(workspace_root)
|
||||||
result = service.get_content(params.filename)
|
result = service.get_content(params.filename, category=params.category)
|
||||||
return json.dumps({"status": "success", **result}, ensure_ascii=False)
|
return json.dumps({"status": "success", **result}, ensure_ascii=False)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return json.dumps({"status": "error", "message": "invalid filename"})
|
return json.dumps({"status": "error", "message": "invalid filename"})
|
||||||
|
|||||||
Reference in New Issue
Block a user