Polish memory UI and add auto tags

This commit is contained in:
OpenClaw
2026-04-24 21:57:58 +02:00
parent 1edca71327
commit 9fd14f87f7
3 changed files with 528 additions and 624 deletions
+47 -2
View File
@@ -83,6 +83,48 @@ function getEntryCount(content) {
return content.split('\n').filter((line) => line.trim().startsWith('- [')).length
}
const TAG_STOPWORDS = new Set([
'about', 'after', 'again', 'also', 'been', 'being', 'between', 'could', 'daily', 'does', 'done', 'from', 'have', 'into', 'just', 'like', 'main', 'make', 'maybe', 'memory', 'more', 'most', 'much', 'need', 'note', 'notes', 'only', 'other', 'over', 'really', 'same', 'some', 'still', 'than', 'that', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'today', 'very', 'want', 'were', 'what', 'when', 'with', 'work', 'would', 'your', 'yours', 'jimmi', 'rook', 'said', 'todo', 'todos'
])
function generateTags(content, filename) {
const tags = []
const pushTag = (value) => {
const cleaned = String(value || '').toLowerCase().replace(/[^a-z0-9+-]+/g, '').trim()
if (!cleaned || cleaned.length < 3 || TAG_STOPWORDS.has(cleaned) || tags.includes(cleaned)) return
tags.push(cleaned)
}
if (filename === 'MEMORY.md') pushTag('core')
for (const line of content.split('\n')) {
const trimmed = line.trim()
if (trimmed.startsWith('#')) {
trimmed.replace(/^#+\s*/, '').split(/[^a-zA-Z0-9+-]+/).forEach(pushTag)
}
const hashtagMatches = trimmed.match(/#[a-zA-Z0-9+-]+/g) || []
hashtagMatches.forEach((tag) => pushTag(tag.slice(1)))
}
const frequency = new Map()
const normalized = content
.toLowerCase()
.replace(/\[[^\]]*\]/g, ' ')
.replace(/[^a-z0-9+\-\s]/g, ' ')
for (const token of normalized.split(/\s+/)) {
if (!token || token.length < 4 || TAG_STOPWORDS.has(token)) continue
frequency.set(token, (frequency.get(token) || 0) + 1)
}
Array.from(frequency.entries())
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.slice(0, 8)
.forEach(([token]) => pushTag(token))
return tags.slice(0, 4)
}
function formatDateLabel(filename) {
const match = filename.match(/^(\d{4})-(\d{2})-(\d{2})\.md$/)
if (!match) return filename
@@ -206,6 +248,7 @@ function buildFileMeta(doc) {
wordCount: wordCount(content),
entryCount: getEntryCount(content),
dateLabel: formatDateLabel(doc.filename),
tags: generateTags(content, doc.filename),
}
}
@@ -239,6 +282,7 @@ app.get('/api/meta', async (req, res) => {
size: Buffer.byteLength(mainDoc.content || '', 'utf8'),
wordCount: wordCount(mainDoc.content || ''),
entryCount: getEntryCount(mainDoc.content || ''),
tags: generateTags(mainDoc.content || '', 'MEMORY.md'),
} : null
const dailyFiles = dailyDocs.map(buildFileMeta)
@@ -279,7 +323,7 @@ app.get('/api/memories/:filename', async (req, res) => {
await ensureSync()
const doc = await readMemoryDocument(filename)
if (!doc) return res.status(404).json({ error: 'File not found' })
return res.json({ content: doc.content || '' })
return res.json({ content: doc.content || '', tags: generateTags(doc.content || '', filename) })
} catch (error) {
return res.status(500).json({ error: error.message })
}
@@ -290,7 +334,7 @@ app.get('/api/main-memory', async (req, res) => {
await ensureSync()
const doc = await readMemoryDocument('MEMORY.md')
if (!doc) return res.status(404).json({ error: 'MEMORY.md not found' })
res.json({ content: doc.content || '' })
res.json({ content: doc.content || '', tags: generateTags(doc.content || '', 'MEMORY.md') })
} catch (error) {
res.status(500).json({ error: error.message })
}
@@ -369,6 +413,7 @@ app.get('/api/search', async (req, res) => {
dateLabel: formatDateLabel(doc.filename),
entryCount: getEntryCount(content),
wordCount: wordCount(content),
tags: generateTags(content, doc.filename),
matchCount: (lower.match(new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length,
snippet: `${windowStart > 0 ? '…' : ''}${snippet}${windowEnd < content.length ? '…' : ''}`,
matchingLines,