From bbb9a6b61f0b6fd98ed2dc7ce4e227f60b0b0bcc Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 20 Oct 2026 13:33:10 +0800 Subject: [PATCH] rework the memory view and rename profiles to Agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the three separate memory editors with a card layout that shows the source file, a plain-text summary, the last modified time, and the character count for each section, so the stored files are visible without opening them. Rename the sidebar "用户" entry to "Agent" in both locales and add the memory.editTitle, memory.writeTo, and memory.charCount strings. --- packages/client/src/i18n/locales/en.ts | 5 +- packages/client/src/i18n/locales/zh.ts | 5 +- .../client/src/views/hermes/MemoryView.vue | 493 +++++++++++------- 3 files changed, 301 insertions(+), 202 deletions(-) diff --git a/packages/client/src/i18n/locales/en.ts b/packages/client/src/i18n/locales/en.ts index 892128f..2592097 100644 --- a/packages/client/src/i18n/locales/en.ts +++ b/packages/client/src/i18n/locales/en.ts @@ -204,7 +204,7 @@ export default { kanban: 'Kanban', workflow: 'Workflow', models: 'Models', - profiles: 'Profiles', + profiles: 'Agent', skills: 'Skills', plugins: 'Plugins', mcp: 'MCP', @@ -1195,6 +1195,9 @@ export default { title: 'Memory', refresh: 'Refresh', loadFailed: 'Failed to load memory', + editTitle: 'Edit · {name}', + writeTo: 'Write to', + charCount: '{count} chars', myNotes: 'My Notes', noNotes: 'No notes yet.', notesPlaceholder: 'Write your notes...', diff --git a/packages/client/src/i18n/locales/zh.ts b/packages/client/src/i18n/locales/zh.ts index 9b3cd3f..2331d60 100644 --- a/packages/client/src/i18n/locales/zh.ts +++ b/packages/client/src/i18n/locales/zh.ts @@ -204,7 +204,7 @@ export default { kanban: '看板', workflow: '工作流', models: '模型', - profiles: '用户', + profiles: 'Agent', plugins: '插件', mcp: 'MCP', skills: '技能', @@ -1195,6 +1195,9 @@ export default { title: '记忆', refresh: '刷新', loadFailed: '加载记忆失败', + editTitle: '修改 · {name}', + writeTo: '写入文件', + charCount: '{count} 字符', myNotes: '我的笔记', noNotes: '暂无笔记。', notesPlaceholder: '输入笔记内容...', diff --git a/packages/client/src/views/hermes/MemoryView.vue b/packages/client/src/views/hermes/MemoryView.vue index a64c2e9..87a72e2 100644 --- a/packages/client/src/views/hermes/MemoryView.vue +++ b/packages/client/src/views/hermes/MemoryView.vue @@ -2,19 +2,40 @@ import { ref, onMounted, computed } from 'vue' import { useMessage } from '@/composables/useMessage' import { useI18n } from 'vue-i18n' -import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue' import { fetchMemory, saveMemory, type MemoryData } from '@/api/hermes/skills' import { useProfilesStore } from '@/stores/hermes/profiles' +type MemorySection = 'memory' | 'user' | 'soul' + const { t } = useI18n() const message = useMessage() const profilesStore = useProfilesStore() const loading = ref(false) const data = ref(null) -const editingSection = ref<'memory' | 'user' | 'soul' | null>(null) +const editingSection = ref(null) const editContent = ref('') const saving = ref(false) +// 三段记忆固定对应三个文件,落盘路径在服务端决定,前端只做展示。 +const SECTION_FILES: Record = { + memory: 'memories/MEMORY.md', + user: 'memories/USER.md', + soul: 'SOUL.md', +} + +const SECTIONS: { key: MemorySection; labelKey: string }[] = [ + { key: 'memory', labelKey: 'memory.myNotes' }, + { key: 'user', labelKey: 'memory.userProfile' }, + { key: 'soul', labelKey: 'memory.soul' }, +] + +// 各段的修改时间在接口里是平铺字段,key 不可由 section 直接拼出(soul_mtime 等)。 +const MTIME_KEY: Record = { + memory: 'memory_mtime', + user: 'user_mtime', + soul: 'soul_mtime', +} + onMounted(loadMemory) async function loadMemory() { @@ -32,7 +53,7 @@ async function loadMemory() { } } -function startEdit(section: 'memory' | 'user' | 'soul') { +function startEdit(section: MemorySection) { editingSection.value = section editContent.value = data.value?.[section] || '' } @@ -59,7 +80,7 @@ async function handleSave() { } function formatTime(ts: number | null): string { - if (!ts) return '' + if (!ts) return '—' return new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', @@ -68,193 +89,181 @@ function formatTime(ts: number | null): string { }) } -const memoryEmpty = computed(() => !data.value?.memory?.trim()) -const userEmpty = computed(() => !data.value?.user?.trim()) -const soulEmpty = computed(() => !data.value?.soul?.trim()) +// 存储态用 § 分隔段落;展示与摘要都按纯文本处理。 +function plainText(raw: string | undefined): string { + return (raw || '').replace(/§/g, ' ').replace(/\s+/g, ' ').trim() +} -const displayMemory = computed(() => (data.value?.memory || '').replace(/§/g, '\n\n')) -const displayUser = computed(() => (data.value?.user || '').replace(/§/g, '\n\n')) -const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n')) +interface MemoryCard { + key: MemorySection + title: string + file: string + summary: string + mtime: number | null + chars: number + empty: boolean + // 进度条按 28 字符折算 1%,与原型一致。 + percent: number +} + +const cards = computed(() => + SECTIONS.map(({ key, labelKey }) => { + const raw = data.value?.[key] || '' + const plain = plainText(raw) + const chars = raw.length + return { + key, + title: t(labelKey), + file: SECTION_FILES[key], + summary: plain.length > 120 ? `${plain.slice(0, 120)}…` : plain, + mtime: data.value?.[MTIME_KEY[key]] ?? null, + chars, + empty: !raw.trim(), + percent: Math.min(100, Math.max(3, Math.round(chars / 28))), + } + }), +) + +const SECTION_LABEL_KEY: Record = { + memory: 'memory.myNotes', + user: 'memory.userProfile', + soul: 'memory.soul', +} + +const SECTION_PLACEHOLDER_KEY: Record = { + memory: 'memory.notesPlaceholder', + user: 'memory.profilePlaceholder', + soul: 'memory.soulPlaceholder', +} + +const SECTION_EMPTY_KEY: Record = { + memory: 'memory.noNotes', + user: 'memory.noProfile', + soul: 'memory.noSoul', +} + +const editingTitle = computed(() => + editingSection.value + ? t('memory.editTitle', { name: t(SECTION_LABEL_KEY[editingSection.value]) }) + : '', +) + +const editingFile = computed(() => (editingSection.value ? SECTION_FILES[editingSection.value] : '')) + +const editingPlaceholder = computed(() => + editingSection.value ? t(SECTION_PLACEHOLDER_KEY[editingSection.value]) : '', +)